diff --git a/app/build.gradle b/app/build.gradle index 6f5179476..31508fb9e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,7 +59,7 @@ android { stringfog.enable false } release { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release stringfog.enable true @@ -75,8 +75,9 @@ android { } stringfog { + def isRelease = getGradle().getStartParameter().getTaskRequests().toString().contains("release") key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09' - enable true + enable false implementation 'com.github.megatronking.stringfog.xor.StringFogImpl' } @@ -90,6 +91,7 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.github.megatronking.stringfog:xor:1.1.0' implementation 'com.github.piruin:quickaction:2.4.3' + implementation 'com.github.Rosemoe:BlockLinkedList:1.0.0' implementation 'com.github.smart-fun:XmlToJson:1.5.1' // Dependencies for java completion @@ -119,7 +121,6 @@ dependencies { // Local projects implementation project(':treeview') - implementation project(':editor') implementation project(':spinkit') implementation project(':slidr') implementation project(':javadocparser') diff --git a/app/dictionary.txt b/app/dictionary.txt new file mode 100644 index 000000000..8c2a35a51 --- /dev/null +++ b/app/dictionary.txt @@ -0,0 +1,4 @@ +o0OoO0 +0oO00Oo0 +oO0o0O0O +0O0o00 diff --git a/app/libs/ecj.jar b/app/libs/ecj-4.6.1.jar similarity index 87% rename from app/libs/ecj.jar rename to app/libs/ecj-4.6.1.jar index 1c7e9cad3..d0323c672 100644 Binary files a/app/libs/ecj.jar and b/app/libs/ecj-4.6.1.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 385351e30..0945229ca 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,8 @@ -keepattributes SourceFile, LineNumberTable -renamesourcefileattribute FuckYou +-obfuscationdictionary dictionary.txt +-packageobfuscationdictionary dictionary.txt +-classobfuscationdictionary dictionary.txt -ignorewarnings -dontwarn @@ -14,17 +17,14 @@ -dontwarn androidx.lifecycle.** -keep class androidx.arch.** { *; } -keep class androidx.lifecycle.** { *; } --keepclasseswithmembernames,includedescriptorclasses class * { - native ; -} +-keep class org.eclipse.jdt.** { (...); } +-keep class org.eclipse.jdt.** { static *; } + +-keepclasseswithmembernames,includedescriptorclasses class * { native ; } -keep public class * implements com.bumptech.glide.module.GlideModule --keep class * extends com.bumptech.glide.module.AppGlideModule { - (...); -} +-keep class * extends com.bumptech.glide.module.AppGlideModule { (...); } -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { **[] $VALUES; public *; } --keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { - *** rewind(); -} +-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); } diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 24b1824cb..55507826f 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -17,6 +17,8 @@ import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import androidx.transition.Slide; import androidx.transition.TransitionManager; import com.blankj.utilcode.util.ClipboardUtils; @@ -29,6 +31,7 @@ import com.google.android.material.navigation.NavigationView; import com.google.android.material.tabs.TabLayout; import com.google.android.material.transition.MaterialArcMotion; import com.google.android.material.transition.MaterialContainerTransform; +import com.itsaky.androidide.adapters.DiagnosticsAdapter; import com.itsaky.androidide.adapters.EditorPagerAdapter; import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder; import com.itsaky.androidide.app.StudioActivity; @@ -41,9 +44,11 @@ import com.itsaky.androidide.fragments.sheets.OptionsListFragment; import com.itsaky.androidide.fragments.sheets.TextSheetFragment; import com.itsaky.androidide.interfaces.CompileListener; import com.itsaky.androidide.interfaces.DexResultListener; +import com.itsaky.androidide.interfaces.DiagnosticClickListener; import com.itsaky.androidide.language.logs.LogLanguageImpl; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.models.SheetOption; import com.itsaky.androidide.receivers.LogReceiver; @@ -64,6 +69,7 @@ import com.unnamed.b.atv.model.TreeNode; import io.github.rosemoe.editor.langs.EmptyLanguage; import io.github.rosemoe.editor.widget.CodeEditor; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -71,14 +77,14 @@ import java.util.Map; import java.util.regex.Pattern; import me.piruin.quickaction.ActionItem; import me.piruin.quickaction.QuickAction; -import com.elvishew.xlog.XLog; public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener, IDEService.BuildListener, TabLayout.OnTabSelectedListener, NavigationView.OnNavigationItemSelectedListener, DexResultListener, - CompileListener { + CompileListener, + DiagnosticClickListener { private ActivityEditorBinding mBinding; private EditorPagerAdapter mPagerAdapter; @@ -89,6 +95,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private TreeNode mLastHolded; private CodeEditor buildView; private CodeEditor logView; + private RecyclerView diagnosticList; private LogLanguageImpl mLogLanguageImpl; @@ -97,6 +104,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private OptionsListFragment mFileOptionsFragment; private boolean recreateCompletionServices = false; + + private final Map> diagnostics = new HashMap<>(); private final String RES_PATH_REGEX = "/.*/src/.*/res"; private final String LAYOUTRES_PATH_REGEX = "/.*/src/.*/res/layout"; @@ -111,9 +120,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private static final int ACTIONID_CLOSE = 100; private static final int ACTIONID_OTHERS = 101; private static final int ACTIONID_ALL = 102; - - private static final HashMap> mDiagnosticsMap = new HashMap<>(); - + private LogReceiver mLogReceiver = new LogReceiver().setLogListener(line -> appendApkLog(line)); private BroadcastReceiver mServiceStartReceiver = new BroadcastReceiver(){ @@ -186,7 +193,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged()); registerLogReceiver(); - setupContainers(); } @@ -198,6 +204,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(getLogView().getParent() != null && getLogView().getParent() instanceof ViewGroup) { ((ViewGroup) getLogView().getParent()).removeView(getLogView()); } + + if(getDiagnosticsList().getParent() != null && getDiagnosticsList().getParent() instanceof ViewGroup) { + ((ViewGroup) getDiagnosticsList().getParent()).removeView(getDiagnosticsList()); + } mBinding.buildOutcontainer.addView(getBuildView(), new ViewGroup.LayoutParams(-1, -1)); mBinding.buildContainer.setVisibility(View.GONE); @@ -208,12 +218,18 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.logContainer.setVisibility(View.GONE); mBinding.logOutToolbar.setNavigationOnClickListener(v -> hideLogResult()); mBinding.logOutToolbar.setOnClickListener(v -> hideLogResult()); + + mBinding.diagListContainer.addView(getDiagnosticsList(), new ViewGroup.LayoutParams(-1, -1)); + mBinding.diagContainer.setVisibility(View.GONE); + mBinding.diagToolbar.setNavigationOnClickListener(v -> hideDiagnostics()); + mBinding.diagToolbar.setOnClickListener(v -> hideDiagnostics()); mBinding.viewOptionsCard.setVisibility(View.GONE); mBinding.transformScrim.setVisibility(View.GONE); mBinding.transformScrim.setOnClickListener(v -> hideViewOptions()); mBinding.viewBuildOut.setOnClickListener(v -> showBuildResult()); + mBinding.viewDiags.setOnClickListener(v -> showDiagnostics()); mBinding.viewLogs.setOnClickListener(v -> showLogResult()); mBinding.viewFiles.setOnClickListener(v -> showFiles()); mBinding.viewDaemonStatus.setOnClickListener(v -> showDaemonStatus()); @@ -250,18 +266,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F @Override public void onBackPressed() { - if(mBinding.buildContainer.getVisibility() == View.VISIBLE) { - hideBuildResult(); - } else if(mBinding.logContainer.getVisibility() == View.VISIBLE) { - hideLogResult(); - } else if(mBinding.viewOptionsCard.getVisibility() == View.VISIBLE) { - hideViewOptions(); - } else if(mBinding.getRoot().isDrawerOpen(GravityCompat.END)) { + if(mBinding.getRoot().isDrawerOpen(GravityCompat.END)) { mBinding.getRoot().closeDrawer(GravityCompat.END); } else if(mBinding.getRoot().isDrawerOpen(GravityCompat.START)) { mBinding.getRoot().closeDrawer(GravityCompat.START); } else if(getDaemonStatusFragment().isShowing()) { getDaemonStatusFragment().dismiss(); + } else if(mBinding.buildContainer.getVisibility() == View.VISIBLE) { + hideBuildResult(); + } else if(mBinding.logContainer.getVisibility() == View.VISIBLE) { + hideLogResult(); + } else if(mBinding.viewOptionsCard.getVisibility() == View.VISIBLE) { + hideViewOptions(); + } else if(mBinding.diagContainer.getVisibility() == View.VISIBLE) { + hideDiagnostics(); } else if(mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) { mFileOptionsFragment.dismiss(); } else { @@ -411,6 +429,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(recreateCompletionServices) { getApp().createCompletionService(mProject); } else if(task.getType() == GradleTask.Type.BUILD) { + getApp().createJavaCompiler(mProject); + getApp().getJavaCompiler().compileAllAsync(this); if(task.getTaskID() == IDEService.TASK_ASSEMBLE_DEBUG) { if(task.buildsApk()) { install(task.getApk(new File(mProject.getMainModulePath(), "build").getAbsolutePath(), mProject.getMainModule())); @@ -429,6 +449,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(task.canOutput()) { setStatus(msg); } + + if(task.getType() == GradleTask.Type.BUILD) { + getApp().createJavaCompiler(mProject); + getApp().getJavaCompiler().compileAllAsync(this); + } showBuildResult(); @@ -441,9 +466,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F public void onGetDependencies(List dependencies) { setStatus(getString(R.string.msg_starting_completion)); mProject.setClassPaths(dependencies); - getApp().createJavaCompiler(mProject, this); - getApp().getJavaCompiler().compileAll(); - new Jar2DexService(getApp().getIndexedDir(), dependencies, this).startDexing(); + createCompletionServices(); } @Override @@ -464,19 +487,31 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F public void saveFiles() { saveAll(); } - + + /** + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + @Deprecated @Override public void onDex(String name) { setStatus(getString(R.string.msg_indexing, name)); } - + + /** + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + @Deprecated @Override public void onDexSuccess(List dexes) { ConstantsBridge.PROJECT_DEXES = dexes; setStatus(getString(R.string.msg_indexing_success)); createCompletionServices(); } - + + /** + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + @Deprecated @Override public void onDexFailed(String message) { appendBuildOut(message); @@ -484,14 +519,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F showBuildResult(); } - private void appendBuildOut(String str) { - str = str.endsWith("\n") ? str : str.concat("\n"); - getBuildView(true).getText().append(str); + // Can be called from a different different, better to run on UI thread + private void appendBuildOut(final String str) { + runOnUiThread(() -> { + String strFinal = str.endsWith("\n") ? str : str.concat("\n"); + getBuildView(true).getText().append(strFinal); + }); } - private void appendLogOut(String str) { - str = str.endsWith("\n") ? str : str.concat("\n"); - getLogView(true).getText().append(str); + // Can be called from a different different, better to run on UI thread + private void appendLogOut(final String str) { + runOnUiThread(() -> { + String strFinal = str.endsWith("\n") ? str : str.concat("\n"); + getLogView(true).getText().append(strFinal); + }); } private void showFiles() { @@ -563,6 +604,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.logContainer.setVisibility(View.GONE); mBinding.viewOptionsCard.setVisibility(View.VISIBLE); } + + private void showDiagnostics() { + TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.viewDiags, mBinding.diagContainer)); + mBinding.diagContainer.setVisibility(View.VISIBLE); + mBinding.viewOptionsCard.setVisibility(View.GONE); + hideToolbarAndTabs(); + } + + private void hideDiagnostics() { + showToolbarAndTabs(); + TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.diagContainer, mBinding.viewDiags)); + mBinding.diagContainer.setVisibility(View.GONE); + mBinding.viewOptionsCard.setVisibility(View.VISIBLE); + } private MaterialContainerTransform createContainerTransformFor(View start, View end) { return createContainerTransformFor(start, end, mBinding.realContainer); @@ -632,7 +687,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } @Override - public void onCompilationResult(boolean success, Map> diagnostics) { + public void onCompilationResult(boolean singleFile, Map> diagnostics) { + if(getDiagnosticsList().getParent() == null || !(getDiagnosticsList().getParent() instanceof ViewGroup)) + setupContainers(); + getDiagnosticsList().setAdapter(new DiagnosticsAdapter(mapAsGroup(diagnostics), this)); + this.diagnostics.clear(); + this.diagnostics.putAll(diagnostics); provideDiagnosticsToEditors(diagnostics); } @@ -650,6 +710,21 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } } } + + @Override + public void onGroupClick(DiagnosticGroup group) { + if(group != null + && group.file != null + && group.file.exists() + && FileUtils.isUtf8(group.file)) + { + openFile(group.file); + } + } + + @Override + public void onDiagnosticClick(CompilerDiagnostic diagnostic) { + } /***************************** **** Tab Related ************ @@ -690,8 +765,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F final int id = p1.getItemId(); if(id == R.id.editornav_discuss) { getApp().openTelegramGroup(); -// } else if(id == R.id.editornav_suggest) { -// getApp().openIssueTracker(); + } else if(id == R.id.editornav_suggest) { + getApp().openIssueTracker(); } else if(id == R.id.editornav_needHelp) { showNeedHelpDialog(); } else if(id == R.id.editornav_settings) { @@ -796,7 +871,32 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F logView.setTextSize(12); return logView; } - + + private RecyclerView getDiagnosticsList() { + return diagnosticList == null ? createDiagnosticsList() : diagnosticList; + } + + private RecyclerView createDiagnosticsList() { + diagnosticList = new RecyclerView(this); + diagnosticList.setLayoutManager(new LinearLayoutManager(this)); + diagnosticList.setAdapter(new DiagnosticsAdapter(mapAsGroup(this.diagnostics), this)); + return diagnosticList; + } + + private List mapAsGroup(Map> diags) { + List groups = new ArrayList<>(); + if(diags == null || diags.size() <= 0) + return groups; + for(File file : diags.keySet()) { + List fileDiags = diags.get(file); + if(fileDiags == null || fileDiags.size() <= 0) + continue; + DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags); + groups.add(group); + } + return groups; + } + private LogLanguageImpl getLogLanguage() { return mLogLanguageImpl == null ? mLogLanguageImpl = new LogLanguageImpl() : mLogLanguageImpl; } @@ -823,6 +923,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F boolean isGradleSaved = mPagerAdapter.saveAll(); getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS); if(isGradleSaved) notifySyncNeeded(); + if(getApp().getJavaCompiler() != null) + getApp().getJavaCompiler().compileAllAsync(this); return isGradleSaved; } @@ -1148,7 +1250,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.tabs.setupWithViewPager(mBinding.editorViewPager); mBinding.tabs.addOnTabSelectedListener(this); } - + private void closeFile(int index) { closeFile(index, true); } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java new file mode 100644 index 000000000..ec1cebd0e --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java @@ -0,0 +1,65 @@ +package com.itsaky.androidide.adapters; + +import android.graphics.PorterDuff; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import com.itsaky.androidide.R; +import com.itsaky.androidide.databinding.LayoutDiagnosticItemBinding; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import java.util.List; +import javax.tools.Diagnostic; +import com.itsaky.androidide.interfaces.DiagnosticClickListener; + +public class DiagnosticItemAdpater extends RecyclerView.Adapter { + + private List diags; + private DiagnosticClickListener listener; + + public DiagnosticItemAdpater(List diags, DiagnosticClickListener listener) { + this.diags = diags; + this.listener = listener; + } + + @Override + public DiagnosticItemAdpater.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH(LayoutDiagnosticItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticItemAdpater.VH p1, int p2) { + final CompilerDiagnostic diagnostic = diags.get(p2); + final LayoutDiagnosticItemBinding binding = p1.binding; + + binding.icon.setImageResource(getDiagnosticIconId(diagnostic)); + binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), diagnostic.kind() == Diagnostic.Kind.ERROR ? R.color.diagnostic_error : R.color.diagnostic_warning), PorterDuff.Mode.SRC_ATOP); + binding.title.setText(diagnostic.message()); + + binding.getRoot().setOnClickListener(v -> { + if(listener != null) { + listener.onDiagnosticClick(diagnostic); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + private int getDiagnosticIconId(CompilerDiagnostic diagnostic) { + if(diagnostic.kind() == Diagnostic.Kind.ERROR) + return R.drawable.ic_compilation_error; + return R.drawable.ic_info; + } + + public class VH extends RecyclerView.ViewHolder { + private LayoutDiagnosticItemBinding binding; + public VH(LayoutDiagnosticItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + +} diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java new file mode 100644 index 000000000..d5d912e17 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java @@ -0,0 +1,63 @@ +package com.itsaky.androidide.adapters; + +import android.graphics.PorterDuff; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.itsaky.androidide.R; +import com.itsaky.androidide.databinding.LayoutDiagnosticGroupBinding; +import com.itsaky.androidide.interfaces.DiagnosticClickListener; +import com.itsaky.androidide.models.DiagnosticGroup; +import java.util.List; + +public class DiagnosticsAdapter extends RecyclerView.Adapter { + + private final List diags; + private final DiagnosticClickListener listener; + + public DiagnosticsAdapter(List diags, DiagnosticClickListener listener) { + this.diags = diags; + this.listener = listener; + } + + @Override + public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH(LayoutDiagnosticGroupBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) { + final DiagnosticGroup group = diags.get(p2); + final LayoutDiagnosticGroupBinding binding = p1.binding; + + final int color = ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor); + binding.info.icon.setImageResource(group.icon); + binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + binding.info.title.setText(group.text); + binding.info.title.setTextColor(color); + binding.diagnostics.setLayoutManager(new LinearLayoutManager(binding.diagnostics.getContext())); + binding.diagnostics.setAdapter(new DiagnosticItemAdpater(group.diagnostics, listener)); + + binding.info.getRoot().setOnClickListener(v -> { + if(listener != null) { + listener.onGroupClick(group); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + public class VH extends RecyclerView.ViewHolder { + private LayoutDiagnosticGroupBinding binding; + public VH(LayoutDiagnosticGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + +} diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index bdae075e6..bf6931e33 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -11,15 +11,10 @@ import android.text.TextUtils; import androidx.core.app.NotificationManagerCompat; import androidx.multidex.MultiDexApplication; import com.blankj.utilcode.util.EncodeUtils; +import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; import com.blankj.utilcode.util.SPStaticUtils; import com.blankj.utilcode.util.ThrowableUtils; -import com.elvishew.xlog.LogConfiguration; -import com.elvishew.xlog.LogLevel; -import com.elvishew.xlog.XLog; -import com.elvishew.xlog.printer.Printer; -import com.elvishew.xlog.printer.file.FilePrinter; -import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator; import com.google.firebase.messaging.FirebaseMessaging; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; @@ -28,8 +23,10 @@ import com.itsaky.androidide.language.java.manager.JavaCharacter; import com.itsaky.androidide.language.java.provider.JavaCompletionProvider; import com.itsaky.androidide.language.xml.completion.XMLCompletionService; import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.services.MessagingService; +import com.itsaky.androidide.services.compiler.JavaCompilerService; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.FileUtil; @@ -37,16 +34,11 @@ import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.androidide.utils.StudioUtils; import com.itsaky.toaster.Toaster; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import com.itsaky.androidide.models.ConstantsBridge; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import java.io.FileOutputStream; -import java.io.IOException; -import com.itsaky.androidide.services.compiler.JavaCompilerService; -import com.itsaky.androidide.interfaces.CompileListener; -import com.blankj.utilcode.util.FileUtils; public class StudioApp extends MultiDexApplication { @@ -93,10 +85,6 @@ public class StudioApp extends MultiDexApplication newShell(line -> handleLog(line)).bgAppend("logcat -v threadtime"); setupLibsIfNeeded(); - if(isFrameworkInstalled()) { - newShell(t -> XLog.i(t)).bgAppend("printenv > /sdcard/androidideenv.txt && java -version"); - } - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannels(); } @@ -153,41 +141,28 @@ public class StudioApp extends MultiDexApplication } private void initLogger() { - LogConfiguration config = new LogConfiguration.Builder() - .logLevel(DEBUG ? LogLevel.ALL : LogLevel.NONE) - .tag("Android IDE") - .enableThreadInfo() - .enableStackTrace(2) - .enableBorder() - .build(); - Printer filePrinter = new FilePrinter - .Builder(FileUtil.getExternalStorageDir() + "/ide_xlog") - .fileNameGenerator(new DateFileNameGenerator()) - .build(); - XLog.init( - config, - filePrinter); + } private boolean setupLibsIfNeeded() { File lib = Environment.LIBDIR; - return extractLibs(lib); -// if(lib != null && lib.exists() && lib.isDirectory()) { -// boolean libhook, librt, libgcc, libgcc_real, libpthread, libz; -// libhook = librt = libgcc = libgcc_real = libpthread = libz = false; -// File[] list = lib.listFiles(); -// if(list != null) { -// for(File f : list) { -// libhook = libhook == false && f.getName().equals("libhook.so"); -// librt = librt == false && f.getName().equals("librt.so"); -// libpthread = libpthread == false && f.getName().equals("libpthread.so"); -// libgcc = libgcc == false && f.getName().equals("libgcc.so"); -// libgcc_real = libgcc_real = false & f.getName().equals("libgcc_real.so"); -// libz = libz == false && f.getName().equals("libz.so.1"); -// } -// } else return extractLibs(lib); -// return !(libhook && librt && libgcc && libgcc_real && libpthread && libz) ? extractLibs(lib) : true; -// } else return extractLibs(lib); + if(lib != null && lib.exists() && lib.isDirectory()) { + boolean libhook, librt, libgcc, libgcc_real, libpthread, libz, libcpp_shared; + libhook = librt = libgcc = libgcc_real = libpthread = libz = libcpp_shared = false; + File[] list = lib.listFiles(); + if(list != null) { + for(File f : list) { + libhook = libhook == false ? f.getName().equals("libhook.so") : libhook; + librt = librt == false ? f.getName().equals("librt.so") : librt; + libpthread = libpthread == false ? f.getName().equals("libpthread.so") : libpthread; + libgcc = libgcc == false ? f.getName().equals("libgcc.so") : libgcc; + libgcc_real = libgcc_real = false ? f.getName().equals("libgcc_real.so") : libgcc_real; + libz = libz == false ? f.getName().equals("libz.so.1") : libz; + libcpp_shared = libcpp_shared == false ? f.getName().equals("libc++_shared.so") : libcpp_shared; + } + } else return extractLibs(lib); + return !(libhook && librt && libgcc && libgcc_real && libpthread && libz && libcpp_shared) ? extractLibs(lib) : true; + } else return extractLibs(lib); } private boolean extractLibs(File lib) { @@ -248,10 +223,9 @@ public class StudioApp extends MultiDexApplication this.mCompletionProvider.loadProject(project); } - public void createJavaCompiler(AndroidProject project, CompileListener listener) { + public void createJavaCompiler(AndroidProject project) { this.mJavaCompilerService = - new JavaCompilerService(project) - .setListener(listener); + new JavaCompilerService(project); } public JavaCompilerService getJavaCompiler() { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index 300c4b616..b71c30e4e 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -5,21 +5,26 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.itsaky.androidide.adapters.CompletionListAdapter; +import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.databinding.FragmentEditorBinding; import com.itsaky.androidide.fragments.preferences.EditorPreferences; +import com.itsaky.androidide.interfaces.CompileListener; import com.itsaky.androidide.language.groovy.GroovyLanguage; import com.itsaky.androidide.language.java.JavaLanguage; import com.itsaky.androidide.language.xml.XMLLanguage; import com.itsaky.androidide.language.xml.lexer.XMLLexer; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.services.compiler.JavaCompilerService; import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; import com.itsaky.androidide.syntax.colorschemes.SchemeWombat; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.ReadFileTask; +import com.itsaky.androidide.tools.SourceJavaFileObject; import com.itsaky.androidide.utils.FileUtil; import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.androidide.utils.TypefaceUtils; +import com.sun.tools.javac.resources.compiler; import io.github.rosemoe.editor.interfaces.EditorEventListener; import io.github.rosemoe.editor.langs.EmptyLanguage; import io.github.rosemoe.editor.widget.CodeEditor; @@ -28,13 +33,11 @@ import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.Token; -import com.elvishew.xlog.XLog; -import android.text.TextUtils; -import com.itsaky.androidide.services.compiler.JavaCompilerService; -public class EditorFragment extends BaseFragment implements EditorEventListener { +public class EditorFragment extends BaseFragment implements EditorEventListener, CompileListener { public FragmentEditorBinding binding; private File mFile; @@ -43,9 +46,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener private boolean isFirstCreate = false; private static AndroidProject project; - - private List diags; - + public static final String KEY_FILE_PATH = "file_path"; public static final String KEY_PROJECT = "project"; public static final String EXT_JAVA = ".java"; @@ -55,7 +56,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener public static final String EXT_GROOVY = ".groovy"; public static final String EXT_KOTLIN = ".kt"; public static final String EXT_JSON = ".json"; - + public static EditorFragment newInstance(File file, AndroidProject project) { Bundle bundle = new Bundle(); bundle.putString(KEY_FILE_PATH, file.getAbsolutePath()); @@ -100,11 +101,30 @@ public class EditorFragment extends BaseFragment implements EditorEventListener postRead(); }); } + + @Override + public void onCompilationResult(boolean singleFile, Map> diagnostics) { + if(diagnostics != null + && getFile() != null + && diagnostics.containsKey(getFile())) + { + binding.editorCodeEditor.setDiagnostics(diagnostics.get(getFile())); + } + } + + private void requestCompilation() { + final long s = System.currentTimeMillis(); + final SourceJavaFileObject source = new SourceJavaFileObject(getFile(), binding.editorCodeEditor.getText().toString()); + JavaCompilerService compiler = StudioApp.getInstance().getJavaCompiler(); + if(compiler != null) { + compiler.compileSingleAsync(source, this); + } + } public void setDiagnostics(List diags) { - this.diags = diags; - XLog.i("[EditorFragment] Diagnostics for file: " + getFile().getAbsolutePath() - + "\ndiagnostics: " + TextUtils.join("\n", diags)); + if(binding.editorCodeEditor != null) { + binding.editorCodeEditor.setDiagnostics(diags); + } } private void configureEditorIfNeeded() { @@ -176,10 +196,10 @@ public class EditorFragment extends BaseFragment implements EditorEventListener private void postRead() { if (mFile.isFile() && mFile.getName().endsWith(EXT_JAVA)) { - binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(binding.editorCodeEditor, project)); + binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(project)); binding.editorCodeEditor.setColorScheme(new SchemeVS2019()); } else if (mFile.isFile() && mFile.getName().endsWith(EXT_XML)) { - binding.editorCodeEditor.setEditorLanguage(new XMLLanguage(binding.editorCodeEditor)); + binding.editorCodeEditor.setEditorLanguage(new XMLLanguage()); binding.editorCodeEditor.setColorScheme(new SchemeWombat()); } else if (mFile.isFile() && mFile.getName().endsWith(EXT_GRADLE)) { binding.editorCodeEditor.setEditorLanguage(new GroovyLanguage()); @@ -251,6 +271,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener @Override public void afterDelete(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) { isModified = true; + + requestCompilation(); } @Override @@ -263,11 +285,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener if(isOpen && insertedContent.toString().equals("/")) { closeCurrentTag(editor.getText().toString(), endLine, endColumn); } - - JavaCompilerService compiler = getStudioActivity().getApp().getJavaCompiler(); - if(compiler != null) { - compiler.compileAll(); - } + + requestCompilation(); } @Override diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java index 406a877f9..d91f160c1 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java @@ -26,6 +26,7 @@ import com.unnamed.b.atv.view.AndroidTreeView; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import com.blankj.utilcode.util.FileUtils; public class FileTreeFragment extends BottomSheetDialogFragment implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { @@ -86,13 +87,8 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN public void onClick(TreeNode node, Object p2) { final File f = (File) p2; if(f.isFile()) { - if (mFileActionListener != null && f.isFile()) { - for(String ext : compatibleExtensions) { - if(f.getName().endsWith(ext)) { - mFileActionListener.openFile(f); - break; - } - } + if (mFileActionListener != null && FileUtils.isUtf8(f)) { + mFileActionListener.openFile(f); } } else if(f.isDirectory() && f.exists()) { if(node.isExpanded()) { @@ -181,7 +177,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN final File gradleHome = Environment.GRADLE_PROPS_DIR; final File root = StudioApp.getInstance().getRootDir().getParentFile(); File projectDir = new File(mProject.getProjectPath()); - projectDir = root; mRoot = TreeNode.root(projectDir); if(gradleHome.exists() && gradleHome.isDirectory()) { if(!gradleProps.exists()) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java index f55a045ed..85b931607 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java @@ -84,7 +84,7 @@ public class IDEPreferences extends BasePreferenceFragment { screen.addPreference(editor); screen.addPreference(build); -// screen.addPreference(issueTracker); + screen.addPreference(issueTracker); screen.addPreference(telegram); screen.addPreference(changelog); diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java index 219c4dc3e..cc7bd9173 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java @@ -8,7 +8,6 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import com.blankj.utilcode.util.ThrowableUtils; -import com.elvishew.xlog.XLog; import com.itsaky.androidide.R; import com.itsaky.androidide.models.LogLine; diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java index 3919e7aa7..28c164a6e 100644 --- a/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java @@ -6,5 +6,5 @@ import java.util.List; import java.util.Map; public interface CompileListener { - void onCompilationResult(boolean success, Map> diagnostics); + void onCompilationResult(boolean singleFile, Map> diagnostics); } diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java index bd0fb564d..a2ce1ef51 100644 --- a/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java @@ -3,6 +3,13 @@ package com.itsaky.androidide.interfaces; import androidx.core.util.Pair; import java.util.List; +/** + * A callback to listen for updates while dexing JAR files + * + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + +@Deprecated public interface DexResultListener { public void onDex(String name); public void onDexSuccess(List dexes); diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java new file mode 100644 index 000000000..2d8b9dc54 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java @@ -0,0 +1,9 @@ +package com.itsaky.androidide.interfaces; + +import com.itsaky.androidide.models.DiagnosticGroup; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; + +public interface DiagnosticClickListener { + void onGroupClick(DiagnosticGroup group); + void onDiagnosticClick(CompilerDiagnostic diagnostic); +} diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java index c70ba0d5d..5fce68f9b 100644 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java @@ -6,6 +6,7 @@ import com.itsaky.androidide.utils.Either; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; +import io.github.rosemoe.editor.widget.CodeEditor; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -17,7 +18,7 @@ public class GroovyAutoComplete implements AutoCompleteProvider { private static final List OTHERS = createOtherCompletions(); @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { List> result = new ArrayList<>(); for(String artifact : ANDROIDX_ARTIFACTS) { diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java index ff330bb8a..1bbe9c2f9 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java @@ -15,22 +15,23 @@ import java.util.ArrayList; import java.util.List; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import com.itsaky.androidide.utils.Logger; +import java.util.stream.Collectors; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnosticImpl; public class JavaAutoComplete implements AutoCompleteProvider { - - private CodeEditor editor; private JavaLanguageAnalyzer analyzer; - public JavaAutoComplete(CodeEditor editor, JavaLanguageAnalyzer analyzer) { - this.editor = editor; + public JavaAutoComplete(JavaLanguageAnalyzer analyzer) { this.analyzer = analyzer; } @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { Pair, List>> pair = StudioApp.getInstance().getCompletionProvider().getSuggestions(editor); final ArrayList suggestions = pair.first; - final List> diags = pair.second; +// final List> diags = pair.second; +// editor.setDiagnostics(diags.stream().map(CompilerDiagnosticImpl::fromDiagnostic).collect(Collectors.toList())); if(suggestions != null) { List> result = new ArrayList<>(); for(SuggestItem item : suggestions) { diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java index cef9260b5..0f9ff2ad5 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java @@ -21,14 +21,12 @@ public class JavaLanguage implements EditorLanguage private JavaLanguageAnalyzer analyzer; private JavaAutoComplete complete; private AndroidProject project; - private CodeEditor editor; - public JavaLanguage(CodeEditor editor, AndroidProject project) + public JavaLanguage(AndroidProject project) { this.analyzer = new JavaLanguageAnalyzer(); - this.complete = new JavaAutoComplete(editor, analyzer); + this.complete = new JavaAutoComplete(analyzer); this.project = project; - this.editor = editor; } @Override diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java index 95a760d7d..4accb5aaa 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java @@ -49,7 +49,7 @@ public class CompleteThisKeyword extends JavaCompleteMatcherImpl { MethodDescription desc = new MethodDescription( method.getName().toString(), returnType, - method.getModifiers().flags, + (int) method.getModifiers().flags, paramsStr); setInfo(desc, editor, incomplete); result.add(desc); diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java deleted file mode 100644 index 416fd807a..000000000 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.itsaky.androidide.language.java.parser.internal; - -import android.text.TextUtils; -import androidx.annotation.NonNull; -import com.elvishew.xlog.XLog; -import com.itsaky.androidide.models.AndroidProject; -import com.itsaky.androidide.models.ConstantsBridge; -import dalvik.system.DexFile; -import dalvik.system.PathClassLoader; -import java.io.File; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; - -class CompiledClassLoader { - - @NonNull - public ArrayList getCompiledClassesFromProject(@NonNull AndroidProject project) { - ArrayList classes = new ArrayList<>(); - try { - final List dexes = ConstantsBridge.PROJECT_DEXES; - final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader()); - if (dexes == null || dexes.size() <= 0) return classes; - for (String dex : dexes) { - final DexFile d = new DexFile(new File(dex)); - Enumeration entries = d.entries(); - String name = null; - while (entries.hasMoreElements() && (name = entries.nextElement()) != null) { - while(name.startsWith(File.separator)) - name = name.substring(1); - if(name.contains(File.separator)) - name = name.replace(File.separator, "."); - Class load = tryLoad(loader, name); - if (load != null) - classes.add(load); - } - d.close(); - } - - ConstantsBridge.CLASS_LOAD_SUCCESS = true; - return classes; - } catch (Throwable th) { - ConstantsBridge.CLASS_LOAD_SUCCESS = false; - return classes; - } - } - - private Class tryLoad(ClassLoader loader, String name) { - try { - return loader.loadClass(name); - } catch (ClassNotFoundException e) { - return null; - } - } -} diff --git a/editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java rename to app/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java index cc5120073..c9837f6d4 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java @@ -1,26 +1,47 @@ package com.itsaky.androidide.language.java.parser.internal; -import androidx.annotation.Nullable; +import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.util.Pair; import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.itsaky.androidide.interfaces.Filter; import com.itsaky.androidide.language.java.parser.JavacParser; import com.itsaky.androidide.language.java.parser.model.ClassDescription; +import com.itsaky.androidide.language.java.parser.model.FieldDescription; +import com.itsaky.androidide.language.java.parser.model.MethodDescription; import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.utils.Logger; import com.sun.tools.javac.tree.JCTree; +import dalvik.system.DexFile; +import dalvik.system.PathClassLoader; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; +import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo; +import org.eclipse.jdt.internal.compiler.env.IBinaryField; +import org.eclipse.jdt.internal.compiler.env.IBinaryMethod; +import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import com.itsaky.androidide.language.java.parser.model.ConstructorDescription; +import com.itsaky.androidide.utils.Either; +import com.blankj.utilcode.util.ThrowableUtils; +import com.itsaky.androidide.utils.Environment; public class JavaClassManager implements IClassManager { - private static final String TAG = "JavaClassReader"; private static final String JAVA_DOT_LANG_DOT = "java.lang."; private static JavaClassManager INSTANCE; @@ -65,11 +86,9 @@ public class JavaClassManager implements IClassManager { public void loadFromProject(AndroidProject project) { try { - CompiledClassLoader classLoader = new CompiledClassLoader(); - ArrayList classes = classLoader.getCompiledClassesFromProject(project); - for (Class clazz : classes) { - getClassWrapper(clazz); - } + try { + loadClasspaths(project); + } catch (Throwable e) {} JavacParser parser = new JavacParser(); List javaSrcDirs = project.getSourcePaths(); for (String dir : javaSrcDirs) { @@ -84,9 +103,7 @@ public class JavaClassManager implements IClassManager { } } } - } catch (Throwable e) { - e.printStackTrace(); - } + } catch (Throwable e) {} } /** @@ -249,5 +266,214 @@ public class JavaClassManager implements IClassManager { } return result; } + + private void loadClasspaths(AndroidProject project) throws IOException, ClassFormatException, ClassNotFoundException { + final List classpaths = project.getClassPaths(); + if(!classpaths.contains(Environment.BOOTCLASSPATH)) + classpaths.add(Environment.BOOTCLASSPATH.getAbsolutePath()); + for(int i=0;i entries = zip.entries(); + + ZipEntry entry = null; + while(entries.hasMoreElements() && ((entry = entries.nextElement()) != null)) { + if(!entry.getName().endsWith(".class")) + continue; + try { + ClassFileReader reader = ClassFileReader.read(zip, entry.getName()); + describe(zip, reader); + } catch (Throwable th) {} + } + } + } + + private ClassDescription describe(ZipFile zip, IBinaryType type) throws IOException, ClassFormatException, ClassNotFoundException { + if(type == null) + return null; + final char[] name = type.getName(); + final char[] superClassName = type.getSuperclassName(); + if(name == null) return null; + final int mods = type.getModifiers(); + final boolean isPremitive = false; + final boolean isAnnotation = false; + final boolean isEnum = false; + final boolean isMember = type.getEnclosingTypeName() != null; + final IBinaryField[] fields = type.getFields(); + final IBinaryMethod[] methods = type.getMethods(); + final char[][] interfaces = type.getInterfaceNames(); + ClassDescription clazz = new ClassDescription(bytecodeToSourceType(new String(name)), mods, isPremitive, isAnnotation, isEnum, isMember); + + int index = 0; + while(fields != null && index < fields.length) { + IBinaryField f = fields[index]; + if(f == null) continue; + FieldDescription field = describe(zip, f); + if(field == null) continue; + clazz.addField(field); + ++index; + } + + index = 0; + while(methods != null && index < methods.length) { + IBinaryMethod m = methods[index]; + if(m == null) continue; + Either result = describe(zip, m); + if(result == null) continue; + if(result.isLeft()) + clazz.addMethod(result.getLeft()); + else clazz.addConstructor(result.getRight()); + ++index; + } + + // TODO: Add interfaces + + clazz.setSuperclass(superClassName == null ? ClassDescription.ofObject() : findType(zip, new String(superClassName))); + + update(clazz); + return clazz; + } + + private Either describe(ZipFile zip, IBinaryMethod m) throws IOException, IllegalArgumentException, ClassFormatException, ClassNotFoundException { + if(zip == null || m == null) return null; + final char[] descriptor = m.getMethodDescriptor(); + final char[] signature = m.getGenericSignature(); + final char[] name = m.getSelector(); + final int mods = m.getModifiers(); + if(name == null || descriptor == null) return null; + IClass returns = findType(zip, Signature.getReturnType(new String(descriptor))); + String[] paramTypes = Signature.getParameterTypes(new String(descriptor)); + ArrayList params = new ArrayList<>(); + for(String type : paramTypes) { + params.add(bytecodeToSourceType(type)); + } + return m.isConstructor() ? Either.forRight(new ConstructorDescription(new String(name).replace("/", "."), params)) : Either.forLeft(new MethodDescription(new String(name).replace("/", "."), returns, mods, params)); + } + + private FieldDescription describe(ZipFile zip, IBinaryField field) throws IOException, ClassFormatException, ClassNotFoundException { + if(field == null) + return null; + final char[] n = field.getName(); + final char[] t = field.getTypeName(); + if(n == null || t == null) return null; + final String name = new String(n); + final String type = new String(t); + final int mods = field.getModifiers(); + final String value = field instanceof FieldInfo && ((FieldInfo) field).hasConstant() ? field.getConstant().stringValue() : ""; + IClass typeClass = findType(zip, type); + if(typeClass == null) + typeClass = new ClassDescription(Object.class); + FieldDescription f = new FieldDescription(mods, typeClass, name.replace("/", "."), value); + return f; + } + + private IClass findType(ZipFile zip, String name) throws IOException, ClassFormatException, ClassNotFoundException { + if(zip == null || name == null) + return null; + final int arrayCount = Signature.getArrayCount(name); + if(arrayCount == 0) { + if(name.length() == 1) { + // A primitive type + final Class load = primitiveType(name.charAt(0)); + if(load != null) { + ClassDescription desc = new ClassDescription(load); + desc.initMembers(load); + update(desc); + return desc; + } + } + IClass found = getParsedClass(bytecodeToSourceType(name)); + if(found != null) { + return found; + } else { + ClassFileReader reader = ClassFileReader.read(zip, name); + if(reader == null) + return null; + else { + // This class will be updated in describe() method + return describe(zip, reader); + } + } + } else { + if(Signature.getElementType(name).length() == 1) { + // This type may be an array of primitive + Class load = Class.forName(name); + if(load != null) { + ClassDescription desc = new ClassDescription(load); + desc.initMembers(load); + update(desc); + return desc; + } + } + // TODO: Load array type classes of objects + // Return without loading members of this class + return new ClassDescription(Void.TYPE); + } + } + + private String bytecodeToSourceType(String type) { + type = Signature.getElementType(type); + if(type.startsWith("L") && type.endsWith(";")) { + type = type.substring(1, type.lastIndexOf(";")); + } + return type.replace("/", "."); + } + + public Class primitiveType(char typeId) { + switch (typeId) { + case 'I' : return int.class; + case 'B' : return byte.class; + case 'S' : return short.class; + case 'C' : return char.class; + case 'F' : return float.class; + case 'D' : return double.class; + case 'Z' : return boolean.class; + case 'J' : return long.class; + default : return null; + } + } + + + /** + * Kept as a backup if loading jar files fails for some reasons + */ + private ArrayList loadFromDexes(AndroidProject project) { + ArrayList classes = new ArrayList<>(); + try { + final List dexes = ConstantsBridge.PROJECT_DEXES; + final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader()); + if (dexes == null || dexes.size() <= 0) return classes; + for (String dex : dexes) { + final DexFile d = new DexFile(new File(dex)); + Enumeration entries = d.entries(); + String name = null; + while (entries.hasMoreElements() && (name = entries.nextElement()) != null) { + while(name.startsWith(File.separator)) + name = name.substring(1); + if(name.contains(File.separator)) + name = name.replace(File.separator, "."); + Class load = tryLoad(loader, name); + if (load != null) + classes.add(load); + } + d.close(); + } + + ConstantsBridge.CLASS_LOAD_SUCCESS = true; + return classes; + } catch (Throwable th) { + ConstantsBridge.CLASS_LOAD_SUCCESS = false; + return classes; + } + } + + private Class tryLoad(ClassLoader loader, String name) { + try { + return loader.loadClass(name); + } catch (ClassNotFoundException e) { + return null; + } + } } diff --git a/editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java rename to app/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java index c5fc18eb3..0d9c3b8ef 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java @@ -56,6 +56,12 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { this.isMemberClass = isMember; this.mEum = isEnum; } + + public static ClassDescription ofObject() { + ClassDescription desc = new ClassDescription(Object.class); + desc.initMembers(Object.class); + return desc; + } @Override public boolean isMemberClass() { @@ -251,7 +257,6 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { for (Class anInterface : interfaces) { IClass implement = JavaClassManager.getInstance().getClassWrapper(anInterface); mImplements.add(implement); -// initClassMembers(implement); } if (c.getSuperclass() != null) { @@ -262,8 +267,6 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { mSuperClass = JavaClassManager.getInstance().getParsedClass(Object.class.getName()); } } - -// initClassMembers(mSuperClass); for (Constructor constructor : c.getConstructors()) { if (Modifier.isPublic(constructor.getModifiers())) { @@ -288,26 +291,5 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { public void setSuperclass(IClass superclass) { this.mSuperClass = superclass; -// initClassMembers(mSuperClass); } - -// private void initClassMembers(IClass clazz) { -// if(clazz.getFields() != null) { -// for(IField f : clazz.getFields()) { -// addField(f); -// } -// } -// -// if(clazz.getMethods() != null) { -// for(IMethod method : clazz.getMethods()) { -// addMethod(method); -// } -// } -// -// if(clazz.getConstructors() != null) { -// for(ConstructorDescription desc : clazz.getConstructors()) { -// addConstructor(desc); -// } -// } -// } } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java index 1deccde11..784f6c106 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java @@ -13,21 +13,26 @@ import io.github.rosemoe.editor.widget.CodeEditor; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class ConstructorDescription extends JavaSuggestItemImpl implements IJavaDocCommentable { - private ArrayList mParameterTypes = new ArrayList<>(); + private ArrayList mParameterTypes = new ArrayList<>(); private String mConstructorName; public ConstructorDescription(Constructor constructor) { mConstructorName = constructor.getName(); Class[] parameterTypes = constructor.getParameterTypes(); for (Class parameterType : parameterTypes) { - IClass type = JavaClassManager.getInstance().getClassWrapper(parameterType); - mParameterTypes.add(type); + mParameterTypes.add(parameterType.getName()); } } public ConstructorDescription(String name, List paramTypes) { + mConstructorName = name; + mParameterTypes.addAll(paramTypes.stream().filter(c -> c != null).map(c -> c.getFullClassName()).collect(Collectors.toList())); + } + + public ConstructorDescription(String name, ArrayList paramTypes) { mConstructorName = name; mParameterTypes.addAll(paramTypes); } @@ -95,21 +100,21 @@ public class ConstructorDescription extends JavaSuggestItemImpl implements IJava return getSimpleName() + "()"; } - private String paramsToString(@NonNull ArrayList parameterTypes) { + private String paramsToString(@NonNull ArrayList parameterTypes) { StringBuilder result = new StringBuilder(); boolean firstTime = true; - for (IClass parameterType : parameterTypes) { + for (String parameterType : parameterTypes) { if (firstTime) { firstTime = false; } else { result.append(","); } - result.append(parameterType.getSimpleName()); + result.append(parameterType); } return result.toString(); } - public ArrayList getParameterTypes() { + public ArrayList getParameterTypes() { return mParameterTypes; } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java index 2080ca446..e7658e479 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java @@ -28,10 +28,10 @@ public class MethodDescription extends JavaSuggestItemImpl implements Member, Su private IClass mReturnType; public MethodDescription(String name, IClass returnType, - long modifiers, ArrayList parameterTypes) { + int modifiers, ArrayList parameterTypes) { mMethodName = name; mReturnType = returnType; - mModifiers = (int) modifiers; + mModifiers = modifiers; mParameterTypes = parameterTypes; } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java b/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java index 330ed5bd8..e52afeb8c 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java @@ -44,7 +44,7 @@ public class JavaCompletionProvider { this.mPackageManager = new PackageManager(); this.mJavaParser = new JavacParser(); - addAutoComplete(); + addMatchers(); } public boolean loadProject(AndroidProject project) { @@ -57,7 +57,7 @@ public class JavaCompletionProvider { return initiated; } - private void addAutoComplete() { + private void addMatchers() { mCompletors.add(new CompleteExpression(mClassLoader)); mCompletors.add(new CompleteNewKeyword(mClassLoader)); mCompletors.add(new CompletePackage(mPackageManager)); diff --git a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java index cb8268950..2f5a19cd7 100644 --- a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java +++ b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java @@ -10,6 +10,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; +import io.github.rosemoe.editor.widget.CodeEditor; import io.github.rosemoe.editor.widget.SymbolPairMatch; import java.util.ArrayList; import java.util.List; @@ -90,7 +91,7 @@ public class LogLanguageImpl implements EditorLanguage { private static class LogCompletor implements AutoCompleteProvider { @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { return new ArrayList>(); } } diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java index 541bda0fa..d8131c66b 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java @@ -18,8 +18,6 @@ import com.itsaky.androidide.utils.EitherOfResult; public class XMLAutoComplete implements AutoCompleteProvider { - private CodeEditor editor; - private Comparator RESULTS_SORTER = new Comparator(){ @Override @@ -27,13 +25,9 @@ public class XMLAutoComplete implements AutoCompleteProvider { return ((CompletionListItem) p1).getSortText().compareTo(((CompletionListItem) p2).getSortText()); } }; - - public XMLAutoComplete(CodeEditor editor) { - this.editor = editor; - } @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { // We do not store the completion service here in this class // This is because, if we modify the instance of the service in app, // it'll be updated here diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java index ca753a41d..e4aad94d9 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java @@ -19,8 +19,8 @@ public class XMLLanguage implements EditorLanguage { private XMLAutoComplete completer; private NewlineHandler[] newlineHandlers = new NewlineHandler[] {}; - public XMLLanguage(CodeEditor editor) { - this.completer = new XMLAutoComplete(editor); + public XMLLanguage() { + this.completer = new XMLAutoComplete(); this.analyzer = new XMLAnalyzer(); this.newlineHandlers = new NewlineHandler[]{new IndentHandler()}; } diff --git a/editor/src/main/java/com/itsaky/androidide/models/CompletionEdit.java b/app/src/main/java/com/itsaky/androidide/models/CompletionEdit.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/models/CompletionEdit.java rename to app/src/main/java/com/itsaky/androidide/models/CompletionEdit.java diff --git a/editor/src/main/java/com/itsaky/androidide/models/CompletionListItem.java b/app/src/main/java/com/itsaky/androidide/models/CompletionListItem.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/models/CompletionListItem.java rename to app/src/main/java/com/itsaky/androidide/models/CompletionListItem.java diff --git a/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java new file mode 100644 index 000000000..89acdd8dc --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java @@ -0,0 +1,18 @@ +package com.itsaky.androidide.models; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import java.io.File; +import java.util.List; + +public class DiagnosticGroup { + public int icon; + public String text; + public File file; + public List diagnostics; + + public DiagnosticGroup(int icon, File file, List diagnostics) { + this.icon = icon; + this.file = file; + this.text = file.getName(); + this.diagnostics = diagnostics; + } +} diff --git a/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java b/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java index 4f8bbe6d9..134b1bdd7 100644 --- a/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java +++ b/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java @@ -4,9 +4,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.itsaky.androidide.models.LogLine; +import com.itsaky.androidide.utils.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.elvishew.xlog.XLog; public class LogReceiver extends BroadcastReceiver { @@ -37,7 +37,7 @@ public class LogReceiver extends BroadcastReceiver { Matcher m = LOG_HEAD_PATTERN.matcher(line); LogLine log = null; final boolean find = line != null && line.trim().length() > 2 /*if length() == 2 then only [] is available*/ && m.find(); - XLog.i("line -> " + line + "\nlogTag find() -> " + find); + Logger.instance().i("line -> " + line + "\nlogTag find() -> " + find); if(find) { String date = ""; String time = ""; @@ -84,9 +84,9 @@ public class LogReceiver extends BroadcastReceiver { log = new LogLine("", "", "", "", "", "", line); } - XLog.i("log = " + log + "\nlogListener = " + listener); + Logger.instance().i("log = " + log + "\nlogListener = " + listener); if(log != null && listener != null) { - XLog.i("listener.appendLogLine(...) log ->\n" + log); + Logger.instance().i("listener.appendLogLine(...) log ->\n" + log); listener.appendLogLine(log); logTag = null; } diff --git a/app/src/main/java/com/itsaky/androidide/services/IDEService.java b/app/src/main/java/com/itsaky/androidide/services/IDEService.java index 2d4fb802a..393cffd4c 100644 --- a/app/src/main/java/com/itsaky/androidide/services/IDEService.java +++ b/app/src/main/java/com/itsaky/androidide/services/IDEService.java @@ -207,6 +207,8 @@ public class IDEService extends Service implements ShellServer.Callback { @Override public void onDestroy() { super.onDestroy(); + + // Delete the tmp directory. This directory is not needed anymore... FileUtils.delete(Environment.TMP_DIR); isRunning = false; } diff --git a/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java b/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java index 1073cd147..7e6643bf1 100644 --- a/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java +++ b/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java @@ -12,6 +12,12 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +/** + * A utility class to convert JAR files to DEX + * + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ +@Deprecated public class Jar2DexService implements ShellServer.Callback { private final List mClasspaths; diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java new file mode 100644 index 000000000..99f9a8e92 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java @@ -0,0 +1,33 @@ +package com.itsaky.androidide.services.compiler; + +import org.eclipse.jdt.core.compiler.CompilationProgress; + +public class CompilerProgress extends CompilationProgress { + + private boolean canceled = false; + + public void setCanceled(boolean canceled) { + this.canceled = canceled; + } + + @Override + public void begin(int p1) { + } + + @Override + public void done() { + } + + @Override + public boolean isCanceled() { + return canceled; + } + + @Override + public void setTaskName(String p1) { + } + + @Override + public void worked(int p1, int p2) { + } +} diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java new file mode 100644 index 000000000..f644b19b0 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java @@ -0,0 +1,101 @@ +package com.itsaky.androidide.services.compiler; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileObject; + +public class CompilerRequestor implements ICompilerRequestor { + + private DiagnosticListener listener; + + public CompilerRequestor(DiagnosticListener listener) { + this.listener = listener; + } + + @Override + public void acceptResult(CompilationResult p1) { + if (p1 != null && p1.hasProblems()) { + for (CategorizedProblem problem : p1.getAllProblems()) { + report(problem); + } + } + } + + private void report(final CategorizedProblem problem) { + this.listener.report(new Diagnostic() { + @Override + public String getCode() { + return null; + } + @Override + public long getColumnNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).column; + } + return Diagnostic.NOPOS; + } + @Override + public long getEndPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceEnd(); + } + return Diagnostic.NOPOS; + } + @Override + public Kind getKind() { + if (problem.isError()) { + return Diagnostic.Kind.ERROR; + } + if (problem.isWarning()) { + return Diagnostic.Kind.WARNING; + } else if (problem instanceof DefaultProblem && ((DefaultProblem) problem).isInfo()) { + return Diagnostic.Kind.NOTE; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceLineNumber(); + } + return Diagnostic.NOPOS; + } + @Override + public String getMessage(Locale locale) { + return problem.getMessage(); + } + @Override + public long getPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceStart(); + } + return Diagnostic.NOPOS; + } + @Override + public JavaFileObject getSource() { + if (problem instanceof DefaultProblem) { + File f = new File(new String(problem.getOriginatingFileName())); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + return null; + } + @Override + public long getStartPosition() { + return getPosition(); + } + }); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java new file mode 100644 index 000000000..fcdf35c50 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java @@ -0,0 +1,138 @@ +package com.itsaky.androidide.services.compiler; + +import java.io.PrintWriter; +import org.eclipse.jdt.core.compiler.CompilationProgress; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.Compiler; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.ReadManager; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException; +import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; +import org.eclipse.jdt.internal.compiler.util.Messages; + +public class CompilerWrapper extends Compiler { + + public CompilerWrapper(INameEnvironment env, + IErrorHandlingPolicy errorHandlingPolicy, + CompilerOptions options, + ICompilerRequestor compilerRequestor, + IProblemFactory problemFactory, + PrintWriter out, + CompilationProgress progress) { + super(env, errorHandlingPolicy, options, compilerRequestor, problemFactory, out, progress); + this.useSingleThread = true; + } + + public CompilerWrapper setProgress(CompilationProgress progress) { + this.progress = progress; + return this; + } + + @Override + public void compile(ICompilationUnit[] sourceUnits) { + compileInternal(sourceUnits, false); + } + + private void compileInternal(ICompilationUnit[] sourceUnits, boolean lastRound) { + this.stats.startTime = System.currentTimeMillis(); + try { + reportProgress(Messages.compilation_beginningToCompile); + if (this.annotationProcessorManager == null) { + beginToCompile(sourceUnits); + } else { + ICompilationUnit[] originalUnits = sourceUnits.clone(); // remember source units in case a source type collision occurs + try { + beginToCompile(sourceUnits); + if (!lastRound) processAnnotations(); + if (!this.options.generateClassFiles) return; + } catch (SourceTypeCollisionException e) { + backupAptProblems(); + reset(); + int originalLength = originalUnits.length; + int newProcessedLength = e.newAnnotationProcessorUnits.length; + ICompilationUnit[] combinedUnits = new ICompilationUnit[originalLength + newProcessedLength]; + System.arraycopy(originalUnits, 0, combinedUnits, 0, originalLength); + System.arraycopy(e.newAnnotationProcessorUnits, 0, combinedUnits, originalLength, newProcessedLength); + this.annotationProcessorStartIndex = originalLength; + compileInternal(combinedUnits, e.isLastRound); + return; + } + } + restoreAptProblems(); + processCompiledUnits(0, lastRound); + } catch (AbortCompilation e) { + this.handleInternalException(e, null); + } catch (Throwable th) { + AbortCompilation a = new AbortCompilation(); + a.isSilent = true; + handleInternalException(a, null); + } + if (this.options.verbose) { + if (this.totalUnits > 1) { + this.out.println(Messages.bind(Messages.compilation_units, String.valueOf(this.totalUnits))); + } else { + this.out.println(Messages.bind(Messages.compilation_unit, String.valueOf(this.totalUnits))); + } + } + } + + @Override + protected void internalBeginToCompile(ICompilationUnit[] sourceUnits, int maxUnits) { + if (!this.useSingleThread && maxUnits >= ReadManager.THRESHOLD) + this.parser.readManager = new ReadManager(sourceUnits, maxUnits); + + for (int i = 0; i < maxUnits; i++) { + CompilationResult unitResult = null; + try { + if (this.options.verbose) { + this.out.println( + Messages.bind(Messages.compilation_request, + new String[] { + String.valueOf(i + 1), + String.valueOf(maxUnits), + new String(sourceUnits[i].getFileName()) + })); + } + CompilationUnitDeclaration parsedUnit; + unitResult = new CompilationResult(sourceUnits[i], i, maxUnits, this.options.maxProblemsPerUnit); + long parseStart = System.currentTimeMillis(); + if (this.totalUnits < this.parseThreshold) { + parsedUnit = this.parser.parse(sourceUnits[i], unitResult); + } else { + parsedUnit = this.parser.dietParse(sourceUnits[i], unitResult); + } + long resolveStart = System.currentTimeMillis(); + this.stats.parseTime += resolveStart - parseStart; + this.lookupEnvironment.buildTypeBindings(parsedUnit, null); + this.stats.resolveTime += System.currentTimeMillis() - resolveStart; + addCompilationUnit(sourceUnits[i], parsedUnit); + ImportReference currentPackage = parsedUnit.currentPackage; + if (currentPackage != null) { + unitResult.recordPackageName(currentPackage.tokens); + } + if (this.parser.readManager != null) { + this.parser.readManager.shutdown(); + this.parser.readManager = null; + } + this.lookupEnvironment.completeTypeBindings(); + } catch (AbortCompilation a) { + if (a.compilationResult == null) + a.compilationResult = unitResult; + throw a; + } catch (Throwable th) { + AbortCompilation a = new AbortCompilation(); + a.isSilent = true; + throw a; + } finally { + sourceUnits[i] = null; + } + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java b/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java index 1a965dbb7..e4318d42a 100644 --- a/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java @@ -2,13 +2,18 @@ package com.itsaky.androidide.services.compiler; import android.text.TextUtils; import com.blankj.utilcode.util.FileUtils; +import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.interfaces.CompileListener; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; import com.itsaky.androidide.services.compiler.model.CompilerDiagnosticImpl; +import com.itsaky.androidide.services.compiler.request.CompilationRequest; +import com.itsaky.androidide.tools.SourceJavaFileObject; import com.itsaky.androidide.utils.Environment; +import com.itsaky.androidide.utils.Logger; import java.io.File; import java.io.FileFilter; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -18,20 +23,41 @@ import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; +import org.eclipse.jdt.internal.compiler.batch.FileSystem; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; -import com.elvishew.xlog.XLog; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileObject; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; public class JavaCompilerService implements DiagnosticListener { - private AndroidProject project; - - private CompileListener listener; - private EclipseCompiler compiler; - private EclipseFileManager fileManager; - - private List options; - private final Map> diagnostics = new HashMap<>(); + private AndroidProject mProject; + + private EclipseCompiler mCompiler; + private CompilerWrapper mSingleFileCompiler; + private EclipseFileManager mFileManager; + + private FileSystem sf_nameEnvironment; + private CompilerOptions sf_options; + private IProblemFactory sf_problemFactory; + private ICompilerRequestor sf_compilerRequest; + private CompilerProgress sf_progress; + private IErrorHandlingPolicy sf_policy; + private PrintWriter sf_outWriter; + + private List mOptions; + private List mSources; + + private static final Map> mDiagnostics = new HashMap<>(); private FileFilter JAVA_FILTER = new FileFilter(){ @@ -42,90 +68,344 @@ public class JavaCompilerService implements DiagnosticListener { }; public JavaCompilerService(AndroidProject project) { - this.project = project; - - getCompiler(); + this.mProject = project; + this.createOptions(); } - - public JavaCompilerService setListener(CompileListener listener) { - this.listener = listener; - return this; + + private void createOptions() { + this.mOptions = new ArrayList<>(); + + mOptions.add("-1.8"); + mOptions.add("-d"); + mOptions.add(Environment.TMP_DIR.getAbsolutePath()); + mOptions.add("-proc:none"); + mOptions.add("-warn:all"); + mOptions.add("-warn:-nls"); + mOptions.add("-g"); + mOptions.add("-bootclasspath"); + mOptions.add(Environment.BOOTCLASSPATH.getAbsolutePath()); + mOptions.add("-cp"); + mOptions.add(TextUtils.join(File.pathSeparator, mProject.getClassPaths())); + } + + private EclipseCompiler getCompiler() { + return getCompiler(false); } - - private EclipseCompiler getCompiler() { - if(compiler == null) { - this.compiler = new EclipseCompiler(); - this.fileManager = (EclipseFileManager) compiler.getStandardFileManager(this, Locale.US, null); - this.options = new ArrayList<>(); - - options.add("-1.8"); - options.add("-d"); - options.add("none"); - options.add("-proc:none"); - options.add("-warn:all"); - options.add("-g"); - options.add("-bootclasspath"); - options.add(Environment.BOOTCLASSPATH.getAbsolutePath()); - options.add("-cp"); - options.add(TextUtils.join(File.pathSeparator, project.getClassPaths())); - } - - return compiler; + + private EclipseCompiler getCompiler(boolean forceCreate) { + if(mCompiler == null || forceCreate) { + this.mCompiler = new EclipseCompiler(); + this.mFileManager = (EclipseFileManager) mCompiler.getStandardFileManager(this, Locale.US, null); + } + return mCompiler; } - - public boolean compileSingle(File file) { - List ops = new ArrayList(options); - ops.add("-sourcepath"); - ops.add(TextUtils.join(File.pathSeparator, project.getSourcePaths())); - - JavaCompiler.CompilationTask task = getCompiler().getTask(null, fileManager, this, ops, null, fileManager.getJavaFileObjects(file)); - diagnostics.clear(); - boolean success = task.call(); - if(this.listener != null) { - this.listener.onCompilationResult(success, diagnostics); - } + + private EclipseFileManager getFileManager() { + if(mFileManager == null) + getCompiler(true); + + return mFileManager; + } + + private CompilerWrapper getSingleFileCompiler() { + if(this.sf_nameEnvironment == null) + this.sf_nameEnvironment = new FileSystem(createClasspaths(mProject.getClassPaths()), null, null); + + if(this.sf_compilerRequest == null) + this.sf_compilerRequest = new CompilerRequestor(this); + + if(this.sf_problemFactory == null) + this.sf_problemFactory = getProblemFactory(); + + if(this.sf_options == null) { + this.sf_options = new CompilerOptions(); + putOptions(sf_options); + } + + if(sf_policy == null) + this.sf_policy = DefaultErrorHandlingPolicies.exitAfterAllProblems(); + + if(sf_outWriter == null) + sf_outWriter = new PrintWriter(System.out); + + if(mSingleFileCompiler == null) { + mSingleFileCompiler = new CompilerWrapper( + sf_nameEnvironment, + sf_policy, + sf_options, + sf_compilerRequest, + sf_problemFactory, + sf_outWriter, + sf_progress = new CompilerProgress() + ); + } + return mSingleFileCompiler; + } + + private void putOptions(CompilerOptions options) { + final long languageLevel = ClassFileConstants.JDK1_8; + options.sourceLevel = options.targetJDK = options.complianceLevel = languageLevel; + options.processAnnotations = false; + Map map = options.getMap(); + for(String warn : CompilerOptions.warningTokens) { + map.put(warn, CompilerOptions.ENABLED); + } + map.put(CompilerOptions.OPTION_ReportNonExternalizedStringLiteral, CompilerOptions.DISABLED); + map.put(CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.DO_NOT_GENERATE); + map.put(CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.DO_NOT_GENERATE); + map.put(CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.DO_NOT_GENERATE); + options.set(map); + } + + private String[] createClasspaths(List classpaths) { + if (classpaths == null) { + classpaths = new ArrayList<>(); + } + + if(!classpaths.contains(Environment.BOOTCLASSPATH.getAbsolutePath())) { + classpaths.add(Environment.BOOTCLASSPATH.getAbsolutePath()); + } + + String[] result = new String[classpaths.size()]; + return classpaths.toArray(result); + } + + private CompilationRequest createCompilationRequestForAll(CompileListener listener) { + if(mSources == null || mSources.size() <= 0) { + loadSources(); + } + return CompilationRequest.Builder + .newInstance() + .withCallback(listener) + .withFiles(mSources) + .build(); + } + + private void notifyCompiled(CompileListener listener, Map> diags) { + ThreadUtils.runOnUiThread(() -> { + listener.onCompilationResult(true, diags); + }); + } + + protected void cancelCompilation() { + getCompiler().setCompilationCancelled(true); + if(sf_progress != null) { + sf_progress.setCanceled(true); + } + } + + public List getClasspaths() { + return mProject.getClassPaths(); + } + + public void loadSources() { + mSources = new ArrayList(); + for(String path : mProject.getSourcePaths()) { + if(path == null || path.trim().length() <= 0) continue; + File p = new File(path); + if(!p.exists() || !p.isDirectory()) continue; + mSources.addAll(FileUtils.listFilesInDirWithFilter(p, JAVA_FILTER, true)); + } + } + + public void compileAllAsync(CompileListener listener) { + new Thread(() -> { + compile(createCompilationRequestForAll(listener)); + }).start(); + } + + public void compileAsync(CompilationRequest request) { + new Thread(() -> { + compile(request); + }).start(); + } + + public void compileSingleAsync(SourceJavaFileObject source, CompileListener listener) { + new Thread(() -> { + compileSingle(source, listener); + }).start(); + } + + public boolean compile(final CompilationRequest request) { + this.mDiagnostics.clear(); + final List fileObjects = (ArrayList) getFileManager().getJavaFileObjectsFromFiles(request.files()); + JavaCompiler.CompilationTask task = getCompiler() + .setCompilationCancelled() + .newProgress() + .getTask(null, getFileManager(), this, mOptions, null, fileObjects); + boolean success = task.call(); + notifyCompiled(request.callback(), mDiagnostics); return success; - } - - public boolean compileAll() { - List files = new ArrayList(); - for(String path : project.getSourcePaths()) { - if(path == null || path.trim().length() <= 0) continue; - File p = new File(path); - if(!p.exists() || !p.isDirectory()) continue; - files.addAll(FileUtils.listFilesInDirWithFilter(p, JAVA_FILTER, true)); - } - return compile(files); - } - - public boolean compile(List files) { - JavaCompiler.CompilationTask task = getCompiler().getTask(null, fileManager, this, options, null, fileManager.getJavaFileObjectsFromFiles(files)); - diagnostics.clear(); - final long s = System.nanoTime(); - boolean success = task.call(); - XLog.i("[JavaCompiler]" - + "\ncompiled: " + files.size() + " files" - + "\ntimeElapsed: " + (System.nanoTime() - s) + "ns"); - return success; - } - - public Map> getDiagnostics() { - return diagnostics; - } + } + + public void compileSingle(final SourceJavaFileObject source, final CompileListener listener) { + mDiagnostics.clear(); + cancelCompilation(); + if(mDiagnostics != null) mDiagnostics.clear(); + getSingleFileCompiler() + .setProgress(this.sf_progress = new CompilerProgress()) + .compile(new CompilationUnit[]{source.asUnit()}); + notifyCompiled(listener, mDiagnostics); + } @Override public void report(Diagnostic diagnostic) { CompilerDiagnostic d = CompilerDiagnosticImpl.fromDiagnostic(diagnostic); - if(diagnostics.containsKey(d.file())) { - List diags = diagnostics.get(d.file()); + if(mDiagnostics.containsKey(d.file())) { + List diags = mDiagnostics.get(d.file()); if(diags == null) diags = new ArrayList<>(); diags.add(d); - diagnostics.put(d.file(), diags); + mDiagnostics.put(d.file(), diags); } else { List diags = new ArrayList<>(); diags.add(d); - diagnostics.put(d.file(), diags); + mDiagnostics.put(d.file(), diags); } } + + private IProblemFactory getProblemFactory() { + return new DefaultProblemFactory() { + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + return super.createProblem(originatingFileName, problemId, problemArguments, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final int elaborationID, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Info) != 0) { + return Diagnostic.Kind.NOTE; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + return super.createProblem(originatingFileName, problemId, problemArguments, elaborationID, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java index 1c7437096..5c3ae0004 100644 --- a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java @@ -4,11 +4,13 @@ import java.io.File; import javax.tools.Diagnostic; public interface CompilerDiagnostic { - File file(); - long line(); - long column(); - long start(); - long end(); - String message(); - Diagnostic.Kind kind(); + File file(); + long line(); + long column(); + long start(); + long end(); + String message(); + Diagnostic.Kind kind(); + + int color(); } diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java index e4f18610d..5ad0c28b1 100644 --- a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java @@ -15,16 +15,20 @@ public class CompilerDiagnosticImpl implements CompilerDiagnostic { protected long end = 0; protected Diagnostic.Kind kind; + + public static final int COLOR_ERROR = 0xfff44336; + public static final int COLOR_WARNING = 0xffffb300; + public static final int COLOR_NOTE = 0xffffffff; private CompilerDiagnosticImpl(){} private CompilerDiagnosticImpl(Diagnostic diagnostic) { this.file = new File(diagnostic.getSource().toUri()); this.message = diagnostic.getMessage(Locale.US); - this.line = diagnostic.getLineNumber(); + this.line = diagnostic.getLineNumber() - 1; this.column = diagnostic.getColumnNumber(); this.start = diagnostic.getStartPosition(); - this.end = diagnostic.getEndPosition(); + this.end = diagnostic.getEndPosition() + 1; this.kind = diagnostic.getKind(); } @@ -80,9 +84,34 @@ public class CompilerDiagnosticImpl implements CompilerDiagnostic { public String message() { return message; } + + @Override + public int color() { + if(kind() == Diagnostic.Kind.ERROR) { + return COLOR_ERROR; + } else if(kind() == Diagnostic.Kind.WARNING || kind() == Diagnostic.Kind.MANDATORY_WARNING) { + return COLOR_WARNING; + } + return COLOR_NOTE; + } @Override public String toString() { - return "CompilerDiagnosticImpl(file=" + file() + ", line=" + line() + ", column=" + column() + ", start=" + start() + ", end=" + end() + ", message=" + message() + ")"; + return "CompilerDiagnosticImpl(file=" + file() + ", line=" + line() + ", column=" + column() + ", start=" + start() + ", end=" + end() + ", message=" + message() + ", kind=" + kind().toString() + ")"; } + + @Override + public boolean equals(Object obj) { + if(obj instanceof CompilerDiagnosticImpl) { + CompilerDiagnosticImpl that = (CompilerDiagnosticImpl) obj; + return this.file().equals(that.file()) && + this.line() == that.line() && + this.column() == that.column() && + this.start() == that.start() && + this.end() == that.end() && + this.message().equals(that.message()) && + this.kind() == that.kind(); + } + return false; + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java b/app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java new file mode 100644 index 000000000..8d6ccb737 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java @@ -0,0 +1,71 @@ +package com.itsaky.androidide.services.compiler.request; + +import com.itsaky.androidide.interfaces.CompileListener; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import com.itsaky.androidide.tools.SourceJavaFileObject; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface CompilationRequest { + + List files(); + CompileListener callback(); + + public static class Builder { + + private List files; + private CompileListener callback; + + private Builder(){ + files = new ArrayList<>(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder withFiles(File... files) { + if(files == null) { + this.files = new ArrayList<>(); + return this; + } + this.files = new ArrayList<>(); + for(File file : files) { + this.files.add(file); + } + return this; + } + + public Builder withFiles(List files) { + if(files == null) { + this.files = new ArrayList<>(); + return this; + } + this.files = files; + return this; + } + + public Builder withCallback(CompileListener callback) { + this.callback = callback; + return this; + } + + public CompilationRequest build() { + return new CompilationRequest(){ + + @Override + public List files() { + return files; + } + + @Override + public CompileListener callback() { + return callback; + } + }; + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java index 828f99a08..9d7ee42ab 100644 --- a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -2,7 +2,6 @@ package com.itsaky.androidide.shell; import com.blankj.utilcode.util.CloseUtils; import com.blankj.utilcode.util.ThrowableUtils; -import com.elvishew.xlog.XLog; import com.itsaky.androidide.utils.Environment; import java.io.BufferedReader; import java.io.Closeable; @@ -38,25 +37,30 @@ public class ShellServer extends Thread { stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.ANDROID_HOME) + "/cmdline-tools/latest/bin"); stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.ANDROID_HOME) + "/cmake/bin"); + // Provide paths to .so files needed to execute JDK final String lib = Environment.LIBDIR.getAbsolutePath(); stringBuilder.append("\nexport LD_LIBRARY_PATH=" + lib); stringBuilder.append(System.getenv().containsKey("LD_LIBRARY_PATH") ? ":" + System.getenv().get("LD_LIBRARY_PATH") : ""); if(android.os.Build.VERSION.SDK_INT >= 30) { + // Hook calls to malloc() in native binaries + // Required only in Android 11 and up stringBuilder.append("\nexport LD_PRELOAD=" + Environment.LIBHOOKSO.getAbsolutePath()); } + // Monotonic clock fix, needed for JDK stringBuilder.append("\nls -l /system/lib64/libc.so && " + "ln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/librt.so && " + "ln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/libpthread.so"); - + + // Make sure there are proper permissions set to the $HOME directory stringBuilder.append("\nchmod -R 777 $HOME"); - stringBuilder.append("\njava -XshowSettings:all > /sdcard/androidide_jdk_settings.txt"); + + // Execute all commands... append(stringBuilder.toString(), false); } catch (Throwable th) { if (callback != null) { String out = ThrowableUtils.getFullStackTrace(th).concat("\n"); - XLog.e("[ShellServer] error setting up server ->\n" + out); callback.output(out); } } diff --git a/app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java b/app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java new file mode 100644 index 000000000..c6ff217a4 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java @@ -0,0 +1,39 @@ +package com.itsaky.androidide.tools; + +import java.io.File; +import java.net.URI; +import javax.tools.SimpleJavaFileObject; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; + +public class SourceJavaFileObject extends SimpleJavaFileObject { + + private File file; + private String source; + + public SourceJavaFileObject(File file, String source) { + super(URI.create("source"), Kind.SOURCE); + this.file = file; + this.source = source; + } + + public File getFile() { + return this.file; + } + + @Override + public String getName() { + return file.getAbsolutePath(); + } + + @Override + public CharSequence getCharContent(boolean p1) { + return source; + } + + public CompilationUnit asUnit() { + return new CompilationUnit( + getCharContent(true).toString().toCharArray(), + getFile().getAbsolutePath(), + "UTF-8"); + } +} diff --git a/editor/src/main/java/com/itsaky/androidide/utils/Either.java b/app/src/main/java/com/itsaky/androidide/utils/Either.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/utils/Either.java rename to app/src/main/java/com/itsaky/androidide/utils/Either.java diff --git a/app/src/main/java/com/itsaky/androidide/utils/Logger.java b/app/src/main/java/com/itsaky/androidide/utils/Logger.java new file mode 100644 index 000000000..0520f9286 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/Logger.java @@ -0,0 +1,119 @@ +package com.itsaky.androidide.utils; + +import com.elvishew.xlog.LogConfiguration; +import com.elvishew.xlog.LogLevel; +import com.elvishew.xlog.XLog; +import com.elvishew.xlog.printer.Printer; +import com.elvishew.xlog.printer.file.FilePrinter; +import com.elvishew.xlog.printer.file.naming.LevelFileNameGenerator; + +public class Logger { + + private static Logger instance; + private static com.elvishew.xlog.Logger xLogger; + private static String TAG = "AndroidIDE"; + + public static Logger instance() { + return instance == null ? createInstance(TAG) : instance; + } + + public static Logger instance(String tag) { + return instance == null ? createInstance(tag) : instance; + } + + private Logger(String tag) { + this.TAG = tag; + } + + private static Logger createInstance(String tag) { + LogConfiguration config = new LogConfiguration.Builder() + .logLevel(com.itsaky.androidide.BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE) + .tag("AndroidIDE") + .enableThreadInfo() + .enableStackTrace(10) + .disableBorder() + .build(); + Printer filePrinter = new FilePrinter + .Builder(FileUtil.getExternalStorageDir() + "/ide_xlog") + .fileNameGenerator(new LevelFileNameGenerator()) + .cleanStrategy(f -> false) + .build(); + XLog.init(config, filePrinter); + xLogger = XLog.tag("AndroidIDE").build(); + + instance = new Logger(tag); + return instance; + } + + public Logger d(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.d("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.d(sb.toString()); + return this; + } + + public Logger e(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.e("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.e(sb.toString()); + return this; + } + + public Logger v(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.v("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.v(sb.toString()); + return this; + } + + public Logger i(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.i("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.i(sb.toString()); + return this; + } +} diff --git a/editor/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java b/app/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java rename to app/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java similarity index 90% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java index 784ba9db0..88ba413d2 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java +++ b/app/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java @@ -19,6 +19,7 @@ import com.itsaky.androidide.language.java.parser.internal.SuggestItem; import com.itsaky.androidide.utils.Either; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; +import io.github.rosemoe.editor.widget.CodeEditor; import java.util.List; /** @@ -37,7 +38,7 @@ public interface AutoCompleteProvider { * @param line The line of cursor * @return Analyzed items */ - List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line); + List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line); } diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java b/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java similarity index 94% rename from editor/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java rename to app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java index 032d4cb98..0dfcee871 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java +++ b/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java @@ -24,6 +24,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer.AnalyzeThread.Delegate; +import io.github.rosemoe.editor.widget.CodeEditor; import io.github.rosemoe.editor.widget.SymbolPairMatch; import java.util.ArrayList; import java.util.List; @@ -79,7 +80,7 @@ public class EmptyLanguage implements EditorLanguage { public static class EmptyAutoCompleteProvider implements AutoCompleteProvider { @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { return new ArrayList>(); } diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java b/app/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java rename to app/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java b/app/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java rename to app/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java b/app/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java rename to app/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/HexColor.java b/app/src/main/java/io/github/rosemoe/editor/struct/HexColor.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/HexColor.java rename to app/src/main/java/io/github/rosemoe/editor/struct/HexColor.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java b/app/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java rename to app/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/Span.java b/app/src/main/java/io/github/rosemoe/editor/struct/Span.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/Span.java rename to app/src/main/java/io/github/rosemoe/editor/struct/Span.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java b/app/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java rename to app/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/CharPosition.java b/app/src/main/java/io/github/rosemoe/editor/text/CharPosition.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/CharPosition.java rename to app/src/main/java/io/github/rosemoe/editor/text/CharPosition.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/Content.java b/app/src/main/java/io/github/rosemoe/editor/text/Content.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/Content.java rename to app/src/main/java/io/github/rosemoe/editor/text/Content.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/ContentLine.java b/app/src/main/java/io/github/rosemoe/editor/text/ContentLine.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/ContentLine.java rename to app/src/main/java/io/github/rosemoe/editor/text/ContentLine.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/ContentListener.java b/app/src/main/java/io/github/rosemoe/editor/text/ContentListener.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/ContentListener.java rename to app/src/main/java/io/github/rosemoe/editor/text/ContentListener.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/Cursor.java b/app/src/main/java/io/github/rosemoe/editor/text/Cursor.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/Cursor.java rename to app/src/main/java/io/github/rosemoe/editor/text/Cursor.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/FontCache.java b/app/src/main/java/io/github/rosemoe/editor/text/FontCache.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/FontCache.java rename to app/src/main/java/io/github/rosemoe/editor/text/FontCache.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/FormatThread.java b/app/src/main/java/io/github/rosemoe/editor/text/FormatThread.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/FormatThread.java rename to app/src/main/java/io/github/rosemoe/editor/text/FormatThread.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/Indexer.java b/app/src/main/java/io/github/rosemoe/editor/text/Indexer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/Indexer.java rename to app/src/main/java/io/github/rosemoe/editor/text/Indexer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java b/app/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java rename to app/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java b/app/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java rename to app/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java b/app/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java rename to app/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java b/app/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java rename to app/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java b/app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java rename to app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java b/app/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java rename to app/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java rename to app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java rename to app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/TextUtils.java b/app/src/main/java/io/github/rosemoe/editor/text/TextUtils.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/TextUtils.java rename to app/src/main/java/io/github/rosemoe/editor/text/TextUtils.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/UndoManager.java b/app/src/main/java/io/github/rosemoe/editor/text/UndoManager.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/UndoManager.java rename to app/src/main/java/io/github/rosemoe/editor/text/UndoManager.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java b/app/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java rename to app/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/IntPair.java b/app/src/main/java/io/github/rosemoe/editor/util/IntPair.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/IntPair.java rename to app/src/main/java/io/github/rosemoe/editor/util/IntPair.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java b/app/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java rename to app/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/Objects.java b/app/src/main/java/io/github/rosemoe/editor/util/Objects.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/Objects.java rename to app/src/main/java/io/github/rosemoe/editor/util/Objects.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java similarity index 95% rename from editor/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java rename to app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index 97f5fe4d7..ffd1315ca 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -56,7 +56,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; import io.github.rosemoe.editor.interfaces.EditorEventListener; import io.github.rosemoe.editor.interfaces.EditorLanguage; import io.github.rosemoe.editor.interfaces.NewlineHandler; @@ -82,6 +82,13 @@ import java.util.LinkedList; import java.util.List; import com.blankj.utilcode.util.DeviceUtils; import io.github.rosemoe.editor.struct.HexColor; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.PointF; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import javax.tools.Diagnostic; +import java.util.stream.Collectors; +import com.itsaky.androidide.utils.Logger; /** * CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing @@ -219,6 +226,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca private EditorLanguage mLanguage; private long mLastMakeVisible = 0; private EditorAutoCompleteWindow mCompletionWindow; + private DiagnosticWindow mDiagnosticWindow; private EditorTouchEventHandler mEventHandler; private Paint.Align mLineNumberAlign; private GestureDetector mBasicDetector; @@ -243,7 +251,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca KeyMetaStates mKeyMetaStates = new KeyMetaStates(this); private HashMap mLineColors; - + private HashMap> mDiagnostics; + public CodeEditor(Context context) { this(context, null); } @@ -431,6 +440,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca setFocusableInTouchMode(true); mConnection = new EditorInputConnection(this); mCompletionWindow = new EditorAutoCompleteWindow(this); + mDiagnosticWindow = new DiagnosticWindow(this); mVerticalEdgeGlow = new MaterialEdgeEffect(); mHorizontalGlow = new MaterialEdgeEffect(); mOverrideSymbolPairs = new SymbolPairMatch(); @@ -462,6 +472,57 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mErrorColor = Color.parseColor("#FF5252"); mWarningColor = Color.parseColor("#FFD740"); mRect2.set(-1, -1, -1, -1); + + mDiagnostics = new HashMap<>(); + } + + public void showOrHideDiagnosticWindow() { + int line = getCursor().getLeftLine(); + int index = getCursor().getLeft(); + boolean hide = false; + boolean has = mDiagnostics.containsKey(Long.valueOf(line)); + if(has) { + List diags = mDiagnostics.get(Long.valueOf(line)); + if(diags != null) { + diags = diags.stream().filter(d -> d.start() <= index && d.end() >= index).collect(Collectors.toList()); + if(diags != null && diags.size() > 0){ + CompilerDiagnostic d = diags.get(0); + mDiagnosticWindow.setDiagnostic(d); + if(!mDiagnosticWindow.isShowing()) + mDiagnosticWindow.show(); + else updateDiagnosticWindowPosition(); + } else hide = true; + } else hide = true; + } else hide = true; + if(hide && mDiagnosticWindow.isShowing()) { + mDiagnosticWindow.hide(); + } + } + + public void setDiagnostics(List diags) { + this.mDiagnostics = new HashMap<>(); + mapDiagnostics(diags); + } + + private void mapDiagnostics(List diags) { + if(diags == null) + return; + for(int i=0;i ds = mDiagnostics.get(d.line()); + if(ds == null) + ds = new ArrayList<>(); + if(!ds.contains(d)) + ds.add(d); + } else { + List ds = new ArrayList<>(); + ds.add(d); + mDiagnostics.put(Long.valueOf(d.line()), ds); + } + } } public CodeEditor setLineColors(HashMap map) { @@ -1141,7 +1202,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } } - // Draw hex color values + // Draw hex colors if(isLineColorsEnabled() && mLineColors != null && mLineColors.size() > 0) { for(HexColor c : mLineColors.keySet()) { if(c != null && c.isSameLine(line) && mLineColors.get(c) != null) { @@ -1159,7 +1220,35 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } } } - + + // Draw errors and warnings + if(mDiagnostics != null && mDiagnostics.containsKey(Long.valueOf(line))) { + List diags = mDiagnostics.get(Long.valueOf(line)); + if(diags != null && diags.size() > 0) { + for(int i=0;i=1;i--) { + float startSeed = i * seed + sideDiff + left; + float endSeed = startSeed - seed; + if (i == 1) { + endSeed -= sideDiff; + } + path.lineTo(startSeed - halfSeed, innerHeight); + path.lineTo(endSeed, y); + } + + canvas.drawPath(path, paint); + } /** * Draw code block lines on screen @@ -2141,6 +2258,36 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mTextActionPresenter.onExit(); } } + + private void updateDiagnosticWindowPosition() { + float panelX = updateCursorAnchor() + mDpUnit * 20; + float[] rightLayoutOffset = mLayout.getCharLayoutOffset(mCursor.getRightLine(), mCursor.getRightColumn()); + float panelY = rightLayoutOffset[0] - getOffsetY() + getRowHeight() / 2f; + float restY = getHeight() - panelY; + if (restY > mDpUnit * 120) { + restY = mDpUnit * 120; + } else if (restY < mDpUnit * 100) { + float offset = 0; + while (restY < mDpUnit * 100) { + restY += getRowHeight(); + panelY -= getRowHeight(); + offset += getRowHeight(); + } + getScroller().startScroll(getOffsetX(), getOffsetY(), 0, (int) offset, 0); + } + mDiagnosticWindow.setExtendedX(panelX); + mDiagnosticWindow.setExtendedY(panelY); + if (getWidth() < 500 * mDpUnit) { + mDiagnosticWindow.setWidth(getWidth() * 7 / 8); + mDiagnosticWindow.setExtendedX(getWidth() / 8f / 2f); + } else { + int w = getWidth() / 3; + mDiagnosticWindow.setWidth(w * 2); + } + + mDiagnosticWindow.setHeight((int) restY); + mDiagnosticWindow.updatePosition(); + } /** * Apply new position of auto completion window @@ -3227,6 +3374,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca if (mTextActionPresenter != null) { mTextActionPresenter.onExit(); } + + showOrHideDiagnosticWindow(); } /** @@ -3302,6 +3451,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mTextActionPresenter.onBeginTextSelect(); } mEventHandler.notifyTouchedSelectionHandlerLater(); + showOrHideDiagnosticWindow(); } /** @@ -3767,7 +3917,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - + + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI|EditorInfo.IME_FLAG_NO_FULLSCREEN; + outAttrs.initialSelStart = getCursor() != null ? getCursor().getLeft() : 0; outAttrs.initialSelEnd = getCursor() != null ? getCursor().getRight() : 0; outAttrs.initialCapsMode = mConnection.getCursorCapsMode(0); @@ -4075,6 +4227,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca @Override public void afterInsert(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) { + if(mDiagnosticWindow != null) + mDiagnosticWindow.hide(); + // Update spans if (isSpanMapPrepared(true, endLine - startLine)) { if (startLine == endLine) { @@ -4135,6 +4290,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca @Override public void afterDelete(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) { + if(mDiagnosticWindow != null) + mDiagnosticWindow.hide(); + if (isSpanMapPrepared(false, endLine - startLine)) { if (startLine == endLine) { SpanMapUpdater.shiftSpansOnSingleLineDelete(mSpanner.getResult().getSpanMap(), startLine, startColumn, endColumn); diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java b/app/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java rename to app/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java b/app/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java similarity index 98% rename from editor/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java rename to app/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java index 6f995e5f5..ec5279058 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java @@ -24,7 +24,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.itsaky.androidide.language.java.parser.internal.SuggestItem; import com.itsaky.androidide.utils.Either; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; import io.github.rosemoe.editor.struct.CompletionItem; import android.graphics.drawable.Drawable; import androidx.core.content.ContextCompat; diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java new file mode 100644 index 000000000..c1d47315b --- /dev/null +++ b/app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java @@ -0,0 +1,79 @@ +package io.github.rosemoe.editor.widget; + +import android.graphics.PorterDuff; +import android.view.LayoutInflater; +import androidx.core.content.ContextCompat; +import com.itsaky.androidide.R; +import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import javax.tools.Diagnostic; + +public class DiagnosticWindow extends EditorBasePopupWindow { + + private CodeEditor editor; + private CompilerDiagnostic diagnostic; + private LayoutDiagnosticInfoBinding binding; + + public DiagnosticWindow(CodeEditor editor) { + super(editor); + this.editor = editor; + this.binding = LayoutDiagnosticInfoBinding.inflate(LayoutInflater.from(editor.getContext())); + + setContentView(this.binding.getRoot()); + } + + @Override + public void show() { + super.show(); + setupData(); + } + + public void setMaxHeight(int height) { + + } + + @Override + public void updatePosition() { + super.updatePosition(); + setupData(); + } + + private void setupData() { + if(diagnostic != null) { + binding.icon.setImageResource(getDiagnosticIconId()); + binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), R.color.primaryIconColor), PorterDuff.Mode.SRC_ATOP); + binding.errorType.setText(getDiagnosticTypeString()); + binding.errorPosition.setText( + editor.getContext().getString( + com.itsaky.androidide.R.string.diagnostic_position, + ""+ diagnostic.line(), + ""+ diagnostic.column() + ) + ); + + binding.msg.setText(diagnostic.message()); + binding.msg.setTextIsSelectable(true); + } + } + + public DiagnosticWindow setDiagnostic(CompilerDiagnostic diag) { + this.diagnostic = diag; + return this; + } + + private int getDiagnosticIconId() { + if(diagnostic.kind() == Diagnostic.Kind.ERROR) + return R.drawable.ic_compilation_error; + return R.drawable.ic_info; + } + + private String getDiagnosticTypeString() { + if(diagnostic.kind() == Diagnostic.Kind.ERROR) { + return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_error); + } else if(diagnostic.kind() == Diagnostic.Kind.WARNING || diagnostic.kind() == Diagnostic.Kind.MANDATORY_WARNING) { + return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_warning); + } + return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_note); + } + +} diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java similarity index 97% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java index 991008d4a..d7c2b7884 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java @@ -19,14 +19,12 @@ import android.content.Context; import android.graphics.drawable.GradientDrawable; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.Toast; -import com.blankj.utilcode.util.DeviceUtils; +import com.itsaky.androidide.databinding.LayoutCompletionWindowBinding; import com.itsaky.androidide.language.java.parser.internal.SuggestItem; import com.itsaky.androidide.models.CompletionEdit; import com.itsaky.androidide.models.CompletionListItem; import com.itsaky.androidide.utils.Either; -import io.github.rosemoe.editor.databinding.LayoutCompletionWindowBinding; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.CharPosition; @@ -276,7 +274,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow { mAdapter.attachAttributes(this, results); mBinding.list.setAdapter(mAdapter); mCurrent = 0; - float newHeight = mEditor.getDpUnit() * 40 * results.size(); + float newHeight = mEditor.getDpUnit() * mAdapter.getItemHeight() * results.size(); if (isShowing()) { update(getWidth(), (int) Math.min(newHeight, mMaxHeight)); } @@ -307,7 +305,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow { @Override public void run() { try { - displayResults(mLocalProvider.getAutoCompleteItems(mPrefix, mInner, mColors, mLine), mTime); + displayResults(mLocalProvider.getAutoCompleteItems(mEditor, mPrefix, mInner, mColors, mLine), mTime); } catch (Exception e) { e.printStackTrace(); displayResults(new ArrayList<>(), mTime); diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java similarity index 98% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java index d0084ae16..2c6542660 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java @@ -17,7 +17,7 @@ package io.github.rosemoe.editor.widget; import android.view.Gravity; import android.widget.PopupWindow; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; /** * Editor base panel class diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java similarity index 99% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java index cf150bc5f..e62dfa213 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java @@ -24,7 +24,7 @@ import android.view.MotionEvent; import android.view.View; import android.widget.Button; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; /** * This will show when selecting text diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java b/app/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java rename to app/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/Layout.java b/app/src/main/java/io/github/rosemoe/editor/widget/Layout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/Layout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/Layout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java b/app/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java rename to app/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/Row.java b/app/src/main/java/io/github/rosemoe/editor/widget/Row.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/Row.java rename to app/src/main/java/io/github/rosemoe/editor/widget/Row.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java b/app/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java rename to app/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java b/app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java rename to app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java b/app/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java rename to app/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java similarity index 99% rename from editor/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java index 68925021c..63579251c 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java @@ -25,10 +25,8 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; - import com.google.android.material.button.MaterialButton; - -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; public class TextActionPopupWindow extends TextComposeBasePopup implements View.OnClickListener, CodeEditor.EditorTextActionPresenter { private final CodeEditor mEditor; diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java b/app/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java rename to app/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java diff --git a/app/src/main/java/org/eclipse/jdt/core/Signature.java b/app/src/main/java/org/eclipse/jdt/core/Signature.java new file mode 100644 index 000000000..1846d3ff8 --- /dev/null +++ b/app/src/main/java/org/eclipse/jdt/core/Signature.java @@ -0,0 +1,2927 @@ +/******************************************************************************* + * Copyright (c) 2000, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * IBM Corporation - added J2SE 1.5 support + * Stephan Herrmann - Contribution for + * Bug 463533 - Signature.getSignatureSimpleName() returns different results for resolved and unresolved extends + *******************************************************************************/ +package org.eclipse.jdt.core; + +import java.util.ArrayList; + +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; +import org.eclipse.jdt.internal.compiler.util.Util; + +/** + * Provides methods for encoding and decoding type and method signature strings. + *

+ * Signatures obtained from parsing source files (i.e. files with one of the + * {@link JavaCore#getJavaLikeExtensions() Java-like extensions}) differ subtly + * from ones obtained from pre-compiled binary (".class") files in class names are + * usually left unresolved in the former. For example, the normal resolved form + * of the type "String" embeds the class's package name ("Ljava.lang.String;" + * or "Ljava/lang/String;"), whereas the unresolved form contains only what is + * written "QString;". + *

+ *

+ * Generic types introduce to the Java language in J2SE 1.5 add three new + * facets to signatures: type variables, parameterized types with type arguments, + * and formal type parameters. Rich signatures containing these facets + * only occur when dealing with code that makes overt use of the new language + * features. All other code, and certainly all Java code written or compiled + * with J2SE 1.4 or earlier, involved only simple signatures. + *

+ *

+ * Note that the "Q", "!" and "|" formats are specific to Eclipse; the remainder + * are specified in the JVM spec. + *

+ *

+ * The syntax for a type signature is: + *

+ * TypeSignature ::=
+ *     "B"  // byte
+ *   | "C"  // char
+ *   | "D"  // double
+ *   | "F"  // float
+ *   | "I"  // int
+ *   | "J"  // long
+ *   | "S"  // short
+ *   | "V"  // void
+ *   | "Z"  // boolean
+ *   | "T" + Identifier + ";" // type variable
+ *   | "[" + TypeSignature  // array X[]
+ *   | "!" + TypeSignature  // capture-of ?
+ *   | "|" + TypeSignature + (":" + TypeSignature)+ // intersection type
+ *   | ResolvedClassTypeSignature
+ *   | UnresolvedClassTypeSignature
+ *
+ * ResolvedClassTypeSignature ::= // resolved named type (in compiled code)
+ *     "L" + Identifier + OptionalTypeArguments
+ *           ( ( "." | "/" ) + Identifier + OptionalTypeArguments )* + ";"
+ *     | OptionalTypeParameters + "L" + Identifier +
+ *           ( ( "." | "/" ) + Identifier )* + ";"
+ *
+ * UnresolvedClassTypeSignature ::= // unresolved named type (in source code)
+ *     "Q" + Identifier + OptionalTypeArguments
+ *           ( ( "." | "/" ) + Identifier + OptionalTypeArguments )* + ";"
+ *     | OptionalTypeParameters "Q" + Identifier +
+ *           ( ( "." | "/" ) + Identifier )* + ";"
+ *
+ * OptionalTypeArguments ::=
+ *     "<" + TypeArgument+ + ">"
+ *   |
+ *
+ * TypeArgument ::=
+ *   | TypeSignature
+ *   | "*" // wildcard ?
+ *   | "+" TypeSignature // wildcard ? extends X
+ *   | "-" TypeSignature // wildcard ? super X
+ *
+ * OptionalTypeParameters ::=
+ *     "<" + FormalTypeParameterSignature+ + ">"
+ *   |
+ * 
+ *

+ *

+ * Examples: + *

    + *
  • "[[I" denotes int[][]
  • + *
  • "Ljava.lang.String;" denotes java.lang.String in compiled code
  • + *
  • "QString;" denotes String in source code
  • + *
  • "Qjava.lang.String;" denotes java.lang.String in source code
  • + *
  • "[QString;" denotes String[] in source code
  • + *
  • "QMap<QString;*>;" denotes Map<String,?> in source code
  • + *
  • "Qjava.util.List<TV;>;" denotes java.util.List<V> in source code
  • + *
  • "<E;>Ljava.util.List;" denotes <E>java.util.List in source code
  • + *
+ *

+ *

+ * The syntax for a method signature is: + *

+ * MethodSignature ::= OptionalTypeParameters + "(" + ParamTypeSignature* + ")" + ReturnTypeSignature
+ * ParamTypeSignature ::= TypeSignature
+ * ReturnTypeSignature ::= TypeSignature
+ * 
+ *

+ * Examples: + *

    + *
  • "()I" denotes int foo()
  • + *
  • "([Ljava.lang.String;)V" denotes void foo(java.lang.String[]) in compiled code
  • + *
  • "(QString;)QObject;" denotes Object foo(String) in source code
  • + *
+ *

+ *

+ * The syntax for a formal type parameter signature is: + *

+ * FormalTypeParameterSignature ::=
+ *     TypeVariableName + OptionalClassBound + InterfaceBound*
+ * TypeVariableName ::= Identifier
+ * OptionalClassBound ::=
+ *     ":"
+ *   | ":" + TypeSignature
+ * InterfaceBound ::=
+ *     ":" + TypeSignature
+ * 
+ *

+ * Examples: + *

    + *
  • "X:" denotes X
  • + *
  • "X:QReader;" denotes X extends Reader in source code
  • + *
  • "X:QReader;:QSerializable;" denotes X extends Reader & Serializable in source code
  • + *
+ *

+ *

+ * This class provides static methods and constants only. + *

+ *

Note: An empty signature is considered to be syntactically incorrect. So most methods will throw + * an IllegalArgumentException if an empty signature is provided.

+ * + * @noinstantiate This class is not intended to be instantiated by clients. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public final class Signature { + + /** + * Kind constant for an array type signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int ARRAY_TYPE_SIGNATURE = 4; + + /** + * Kind constant for a base (primitive or void) type signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int BASE_TYPE_SIGNATURE = 2; + + private static final char[] BOOLEAN = "boolean".toCharArray(); //$NON-NLS-1$ + + private static final char[] BYTE = "byte".toCharArray(); //$NON-NLS-1$ + + /** + * Character constant indicating an array type in a signature. + * Value is '['. + */ + public static final char C_ARRAY = org.eclipse.jdt.internal.compiler.util.Util.C_ARRAY; + + /** + * Character constant indicating the primitive type boolean in a signature. + * Value is 'Z'. + */ + public static final char C_BOOLEAN = org.eclipse.jdt.internal.compiler.util.Util.C_BOOLEAN; + + /** + * Character constant indicating the primitive type byte in a signature. + * Value is 'B'. + */ + public static final char C_BYTE = org.eclipse.jdt.internal.compiler.util.Util.C_BYTE; + + /** + * Character constant indicating a capture of a wildcard type in a + * signature. Value is '!'. + * @since 3.1 + */ + public static final char C_CAPTURE = org.eclipse.jdt.internal.compiler.util.Util.C_CAPTURE; + + /** + * Character constant indicating the primitive type char in a signature. + * Value is 'C'. + */ + public static final char C_CHAR = org.eclipse.jdt.internal.compiler.util.Util.C_CHAR; + + /** + * Character constant indicating the colon in a signature. + * Value is ':'. + * @since 3.0 + */ + public static final char C_COLON = org.eclipse.jdt.internal.compiler.util.Util.C_COLON; + + /** + * Character constant indicating the dollar in a signature. + * Value is '$'. + */ + public static final char C_DOLLAR = org.eclipse.jdt.internal.compiler.util.Util.C_DOLLAR; + + /** + * Character constant indicating the dot in a signature. + * Value is '.'. + */ + public static final char C_DOT = org.eclipse.jdt.internal.compiler.util.Util.C_DOT; + + /** + * Character constant indicating the primitive type double in a signature. + * Value is 'D'. + */ + public static final char C_DOUBLE = org.eclipse.jdt.internal.compiler.util.Util.C_DOUBLE; + + /** + * Character constant indicating an exception in a signature. + * Value is '^'. + * @since 3.1 + */ + public static final char C_EXCEPTION_START = org.eclipse.jdt.internal.compiler.util.Util.C_EXCEPTION_START; + + /** + * Character constant indicating a bound wildcard type argument + * in a signature with extends clause. + * Value is '+'. + * @since 3.1 + */ + public static final char C_EXTENDS = org.eclipse.jdt.internal.compiler.util.Util.C_EXTENDS; + + /** + * Character constant indicating the primitive type float in a signature. + * Value is 'F'. + */ + public static final char C_FLOAT = org.eclipse.jdt.internal.compiler.util.Util.C_FLOAT; + + /** + * Character constant indicating the end of a generic type list in a + * signature. Value is '>'. + * @since 3.0 + */ + public static final char C_GENERIC_END = org.eclipse.jdt.internal.compiler.util.Util.C_GENERIC_END; + + /** + * Character constant indicating the start of a formal type parameter + * (or type argument) list in a signature. Value is '<'. + * @since 3.0 + */ + public static final char C_GENERIC_START = org.eclipse.jdt.internal.compiler.util.Util.C_GENERIC_START; + + /** + * Character constant indicating the primitive type int in a signature. + * Value is 'I'. + */ + public static final char C_INT = org.eclipse.jdt.internal.compiler.util.Util.C_INT; + + /** + * Character constant indicating an intersection type in a + * signature. Value is '|'. + * @since 3.7.1 + */ + public static final char C_INTERSECTION = '|'; + + /** + * Character constant indicating the primitive type long in a signature. + * Value is 'J'. + */ + public static final char C_LONG = org.eclipse.jdt.internal.compiler.util.Util.C_LONG; + + /** + * Character constant indicating the end of a named type in a signature. + * Value is ';'. + */ + public static final char C_NAME_END = org.eclipse.jdt.internal.compiler.util.Util.C_NAME_END; + + /** + * Character constant indicating the end of a parameter type list in a + * signature. Value is ')'. + */ + public static final char C_PARAM_END = org.eclipse.jdt.internal.compiler.util.Util.C_PARAM_END; + + /** + * Character constant indicating the start of a parameter type list in a + * signature. Value is '('. + */ + public static final char C_PARAM_START = org.eclipse.jdt.internal.compiler.util.Util.C_PARAM_START; + + /** + * Character constant indicating the start of a resolved, named type in a + * signature. Value is 'L'. + */ + public static final char C_RESOLVED = org.eclipse.jdt.internal.compiler.util.Util.C_RESOLVED; + + /** + * Character constant indicating the semicolon in a signature. + * Value is ';'. + */ + public static final char C_SEMICOLON = org.eclipse.jdt.internal.compiler.util.Util.C_SEMICOLON; + + /** + * Character constant indicating the primitive type short in a signature. + * Value is 'S'. + */ + public static final char C_SHORT = org.eclipse.jdt.internal.compiler.util.Util.C_SHORT; + + /** + * Character constant indicating an unbound wildcard type argument + * in a signature. + * Value is '*'. + * @since 3.0 + */ + public static final char C_STAR = org.eclipse.jdt.internal.compiler.util.Util.C_STAR; + + /** + * Character constant indicating a bound wildcard type argument + * in a signature with super clause. + * Value is '-'. + * @since 3.1 + */ + public static final char C_SUPER = org.eclipse.jdt.internal.compiler.util.Util.C_SUPER; + + /** + * Character constant indicating the start of a resolved type variable in a + * signature. Value is 'T'. + * @since 3.0 + */ + public static final char C_TYPE_VARIABLE = org.eclipse.jdt.internal.compiler.util.Util.C_TYPE_VARIABLE; + + /** + * Character constant indicating the start of an unresolved, named type in a + * signature. Value is 'Q'. + */ + public static final char C_UNRESOLVED = org.eclipse.jdt.internal.compiler.util.Util.C_UNRESOLVED; + + /** + * Character constant indicating result type void in a signature. + * Value is 'V'. + */ + public static final char C_VOID = org.eclipse.jdt.internal.compiler.util.Util.C_VOID; + + private static final char[] CAPTURE = "capture-of".toCharArray(); //$NON-NLS-1$ + + /** + * Kind constant for the capture of a wildcard type signature. + * @see #getTypeSignatureKind(String) + * @since 3.1 + */ + public static final int CAPTURE_TYPE_SIGNATURE = 6; + + private static final char[] CHAR = "char".toCharArray(); //$NON-NLS-1$ + + /** + * Kind constant for a class type signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int CLASS_TYPE_SIGNATURE = 1; + + private static final char[] DOUBLE = "double".toCharArray(); //$NON-NLS-1$ + + + private static final char[] EXTENDS = "extends".toCharArray(); //$NON-NLS-1$ + + private static final char[] FLOAT = "float".toCharArray(); //$NON-NLS-1$ + + private static final char[] INT = "int".toCharArray(); //$NON-NLS-1$ + + /** + * Kind constant for the intersection type signature. + * @see #getTypeSignatureKind(String) + * @since 3.7.1 + */ + public static final int INTERSECTION_TYPE_SIGNATURE = 7; + + private static final char[] LONG = "long".toCharArray(); //$NON-NLS-1$ + + private static final char[] SHORT = "short".toCharArray(); //$NON-NLS-1$ + + /** + * String constant for the signature of the primitive type boolean. + * Value is "Z". + */ + public static final String SIG_BOOLEAN = "Z"; //$NON-NLS-1$ + + /** + * String constant for the signature of the primitive type byte. + * Value is "B". + */ + public static final String SIG_BYTE = "B"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type char. + * Value is "C". + */ + public static final String SIG_CHAR = "C"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type double. + * Value is "D". + */ + public static final String SIG_DOUBLE = "D"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type float. + * Value is "F". + */ + public static final String SIG_FLOAT = "F"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type int. + * Value is "I". + */ + public static final String SIG_INT = "I"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type long. + * Value is "J". + */ + public static final String SIG_LONG = "J"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type short. + * Value is "S". + */ + public static final String SIG_SHORT = "S"; //$NON-NLS-1$ + /** String constant for the signature of result type void. + * Value is "V". + */ + public static final String SIG_VOID = "V"; //$NON-NLS-1$ + private static final char[] SUPER = "super".toCharArray(); //$NON-NLS-1$ + /** + * Kind constant for a type variable signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int TYPE_VARIABLE_SIGNATURE = 3; + private static final char[] VOID = "void".toCharArray(); //$NON-NLS-1$ + /** + * Kind constant for a wildcard type signature. + * @see #getTypeSignatureKind(String) + * @since 3.1 + */ + public static final int WILDCARD_TYPE_SIGNATURE = 5; + +// .d> --> > +private static void appendArgumentSimpleNames(char[] name, int start, int end, StringBuffer buffer) { + buffer.append('<'); + int depth = 0; + int argumentStart = -1; + int argumentCount = 0; + for (int i = start; i <= end; i++) { + switch(name[i]) { + case '<' : + depth++; + if (depth == 1) { + argumentStart = i+1; + } + break; + case '>' : + if (depth == 1) { + if (argumentCount > 0) buffer.append(','); + appendSimpleName(name, argumentStart, i-1, buffer); + argumentCount++; + } + depth--; + break; + case ',' : + if (depth == 1) { + if (argumentCount > 0) buffer.append(','); + appendSimpleName(name, argumentStart, i-1, buffer); + argumentCount++; + argumentStart = i+1; + } + break; + } + } + buffer.append('>'); +} + +/** + * Scans the given string for an array type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendArrayTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + return appendArrayTypeSignature(string, start, fullyQualifyTypeNames, buffer, false); +} + +/** + * Scans the given string for an array type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param isVarArgs true if the array type must be displayed as a + * variable argument, false otherwise + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendArrayTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer, boolean isVarArgs) { + int length = string.length; + // need a minimum 2 char + if (start >= length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_ARRAY) { + throw new IllegalArgumentException(); + } + + int index = start; + c = string[++index]; + while(c == C_ARRAY) { + // need a minimum 2 char + if (index >= length - 1) { + throw new IllegalArgumentException(); + } + c = string[++index]; + } + + int e = appendTypeSignature(string, index, fullyQualifyTypeNames, buffer); + + for(int i = 1, dims = index - start; i < dims; i++) { + buffer.append('[').append(']'); + } + + if (isVarArgs) { + buffer.append('.').append('.').append('.'); + } else { + buffer.append('[').append(']'); + } + return e; +} +/** + * Scans the given string for an capture type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendCaptureTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 2 char + if (start >= string.length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_CAPTURE) { + throw new IllegalArgumentException(); + } + buffer.append(CAPTURE).append(' '); + return appendTypeArgumentSignature(string, start + 1, fullyQualifyTypeNames, buffer); +} +/** + * Scans the given string for a class type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a class type signature + * @see Util#scanClassTypeSignature(char[], int) + */ +private static int appendClassTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 3 chars "Lx;" + if (start >= string.length - 2) { + throw new IllegalArgumentException(); + } + // must start in "L" or "Q" + char c = string[start]; + if (c != C_RESOLVED && c != C_UNRESOLVED) { + throw new IllegalArgumentException(); + } + boolean resolved = (c == C_RESOLVED); + boolean removePackageQualifiers = !fullyQualifyTypeNames; + if (!resolved) { + // keep everything in an unresolved name + removePackageQualifiers = false; + } + int p = start + 1; + int checkpoint = buffer.length(); + int innerTypeStart = -1; + boolean inAnonymousType = false; + while (true) { + if (p >= string.length) { + throw new IllegalArgumentException(); + } + c = string[p]; + switch(c) { + case C_SEMICOLON : + // all done + return p; + case C_GENERIC_START : + int e = appendTypeArgumentSignatures(string, p, fullyQualifyTypeNames, buffer); + // once we hit type arguments there are no more package prefixes + removePackageQualifiers = false; + p = e; + break; + case C_DOT : + if (removePackageQualifiers) { + // erase package prefix + buffer.setLength(checkpoint); + } else { + buffer.append('.'); + } + break; + case '/' : + if (removePackageQualifiers) { + // erase package prefix + buffer.setLength(checkpoint); + } else { + buffer.append('/'); + } + break; + case C_DOLLAR : + innerTypeStart = buffer.length(); + inAnonymousType = false; + if (resolved) { + // once we hit "$" there are no more package prefixes + removePackageQualifiers = false; + /** + * Convert '$' in resolved type signatures into '.'. + * NOTE: This assumes that the type signature is an inner type + * signature. This is true in most cases, but someone can define a + * non-inner type name containing a '$'. + */ + buffer.append('.'); + } + break; + default : + if (innerTypeStart != -1 && !inAnonymousType && Character.isDigit(c)) { + inAnonymousType = true; + buffer.setLength(innerTypeStart); // remove '.' + buffer.insert(checkpoint, "new "); //$NON-NLS-1$ + buffer.append("(){}"); //$NON-NLS-1$ + } + if (!inAnonymousType) + buffer.append(c); + innerTypeStart = -1; + } + p++; + } +} +/** + * Scans the given string for an intersection type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendIntersectionTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 2 char + if (start >= string.length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_INTERSECTION) { + throw new IllegalArgumentException(); + } + start = appendClassTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + if (start < string.length - 1) { + start++; + if (string[start] != C_COLON) { + throw new IllegalArgumentException("should be a colon at this location"); //$NON-NLS-1$ + } + while (string[start] == C_COLON) { + buffer.append(" | "); //$NON-NLS-1$ + start = appendClassTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + if (start == string.length - 1) { + return start; + } else if (start > string.length - 1) { + throw new IllegalArgumentException("Should be at the end"); //$NON-NLS-1$ + } + start++; + } + } + return start; +} +private static void appendSimpleName(char[] name, int start, int end, StringBuffer buffer) { + int lastDot = -1, lastGenericStart = -1, lastGenericEnd = -1; + int depth = 0; + if (name[start] == '?') { // wildcard + buffer.append("?"); //$NON-NLS-1$ + int index = consumeWhitespace(name, start+1, end+1); + switch (name[index]) { + case 'e' : + int checkPos = checkName(EXTENDS, name, index, end); + if (checkPos > 0) { + buffer.append(' ').append(EXTENDS).append(' '); + index = consumeWhitespace(name, checkPos, end+1); + } + break; + case 's' : + checkPos = checkName(SUPER, name, index, end+1); + if (checkPos > 0) { + buffer.append(' ').append(SUPER).append(' '); + index = consumeWhitespace(name, checkPos, end+1); + } + break; + } + start = index; // leading segment got processed + } + lastDotLookup: for (int i = end; i >= start; i--) { + switch (name[i]) { + case '.': + if (depth == 0) { + lastDot = i; + char c = name[start]; + if (c == C_EXTENDS || c == C_SUPER) { + buffer.append(c); + } + break lastDotLookup; + } + break; + case '<': + depth--; + if (depth == 0) lastGenericStart = i; + break; + case '>': + if (depth == 0) lastGenericEnd = i; + depth++; + break; + } + } + int nameStart = lastDot < 0 ? start : lastDot+1; + int nameEnd = lastGenericStart < 0 ? end+1 : lastGenericStart; + buffer.append(name, nameStart, nameEnd - nameStart); + if (lastGenericStart >= 0) { + appendArgumentSimpleNames(name, lastGenericStart, lastGenericEnd, buffer); + buffer.append(name, lastGenericEnd+1, end - lastGenericEnd); // copy trailing portion, may contain dimensions + } +} + +/** + * Scans the given string for a type argument signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a type argument signature + * @see Util#scanTypeArgumentSignature(char[], int) + */ +private static int appendTypeArgumentSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 1 char + if (start >= string.length) { + throw new IllegalArgumentException(); + } + char c = string[start]; + switch(c) { + case C_STAR : + buffer.append('?'); + return start; + case C_EXTENDS : + buffer.append("? extends "); //$NON-NLS-1$ + return appendTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + case C_SUPER : + buffer.append("? super "); //$NON-NLS-1$ + return appendTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + default : + return appendTypeSignature(string, start, fullyQualifyTypeNames, buffer); + } +} + +/** + * Scans the given string for a list of type arguments signature starting at the + * given index and appends it to the given buffer, and returns the index of the + * last character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a list of type argument + * signatures + * @see Util#scanTypeArgumentSignatures(char[], int) + */ +private static int appendTypeArgumentSignatures(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 2 char "<>" + if (start >= string.length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_GENERIC_START) { + throw new IllegalArgumentException(); + } + buffer.append('<'); + int p = start + 1; + int count = 0; + while (true) { + if (p >= string.length) { + throw new IllegalArgumentException(); + } + c = string[p]; + if (c == C_GENERIC_END) { + buffer.append('>'); + return p; + } + if (count != 0) { + buffer.append(','); + } + int e = appendTypeArgumentSignature(string, p, fullyQualifyTypeNames, buffer); + count++; + p = e + 1; + } +} +/** + * Scans the given string for a type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a type signature + * @see Util#scanTypeSignature(char[], int) + */ +private static int appendTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + return appendTypeSignature(string, start, fullyQualifyTypeNames, buffer, false); +} + +/** + * Scans the given string for a type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @param isVarArgs true if the type must be displayed as a + * variable argument, false otherwise. In this case, the type must be an array type + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a type signature, or if isVarArgs is true, + * and the type is not an array type signature. + * @see Util#scanTypeSignature(char[], int) + */ +private static int appendTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer, boolean isVarArgs) { + // need a minimum 1 char + if (start >= string.length) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (isVarArgs) { + switch (c) { + case C_ARRAY : + return appendArrayTypeSignature(string, start, fullyQualifyTypeNames, buffer, true); + case C_RESOLVED : + case C_UNRESOLVED : + case C_TYPE_VARIABLE : + case C_BOOLEAN : + case C_BYTE : + case C_CHAR : + case C_DOUBLE : + case C_FLOAT : + case C_INT : + case C_LONG : + case C_SHORT : + case C_VOID : + case C_STAR: + case C_EXTENDS: + case C_SUPER: + case C_CAPTURE: + case C_INTERSECTION : + default: + throw new IllegalArgumentException(); // a var args is an array type + } + } else { + switch (c) { + case C_ARRAY : + return appendArrayTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_RESOLVED : + case C_UNRESOLVED : + return appendClassTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_TYPE_VARIABLE : + int e = Util.scanTypeVariableSignature(string, start); + buffer.append(string, start + 1, e - start - 1); + return e; + case C_BOOLEAN : + buffer.append(BOOLEAN); + return start; + case C_BYTE : + buffer.append(BYTE); + return start; + case C_CHAR : + buffer.append(CHAR); + return start; + case C_DOUBLE : + buffer.append(DOUBLE); + return start; + case C_FLOAT : + buffer.append(FLOAT); + return start; + case C_INT : + buffer.append(INT); + return start; + case C_LONG : + buffer.append(LONG); + return start; + case C_SHORT : + buffer.append(SHORT); + return start; + case C_VOID : + buffer.append(VOID); + return start; + case C_CAPTURE : + return appendCaptureTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_INTERSECTION : + return appendIntersectionTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_STAR: + case C_EXTENDS: + case C_SUPER: + return appendTypeArgumentSignature(string, start, fullyQualifyTypeNames, buffer); + default : + throw new IllegalArgumentException(); + } + } +} + +private static int checkArrayDimension(char[] typeName, int pos, int length) { + int genericBalance = 0; + while (pos < length) { + switch(typeName[pos]) { + case '<' : + genericBalance++; + break; + case ',' : + if (genericBalance == 0) return -1; + break; + case '>': + if (genericBalance == 0) return -1; + genericBalance--; + break; + case '[': + if (genericBalance == 0) { + return pos; + } + } + pos++; + } + return -1; +} +private static int checkName(char[] name, char[] typeName, int pos, int length) { + if (CharOperation.fragmentEquals(name, typeName, pos, true)) { + pos += name.length; + if (pos == length) return pos; + char currentChar = typeName[pos]; + switch (currentChar) { + case ' ' : + case '.' : + case '<' : + case '>' : + case '[' : + case ',' : + return pos; + default: + if (ScannerHelper.isWhitespace(currentChar)) + return pos; + + } + } + return -1; +} +private static int checkNextChar(char[] typeName, char expectedChar, int pos, int length, boolean isOptional) { + pos = consumeWhitespace(typeName, pos, length); + if (pos < length && typeName[pos] == expectedChar) + return pos + 1; + if (!isOptional) throw new IllegalArgumentException(new String(typeName)); + return -1; +} + +private static int consumeWhitespace(char[] typeName, int pos, int length) { + while (pos < length) { + char currentChar = typeName[pos]; + if (currentChar != ' ' && !ScannerHelper.isWhitespace(currentChar)) { + break; + } + pos++; + } + return pos; +} +/** + * Creates a new type signature with the given amount of array nesting added + * to the given type signature. + * + * @param typeSignature the type signature + * @param arrayCount the desired number of levels of array nesting + * @return the encoded array type signature + * + * @since 2.0 + */ +public static char[] createArraySignature(char[] typeSignature, int arrayCount) { + if (arrayCount == 0) return typeSignature; + int sigLength = typeSignature.length; + char[] result = new char[arrayCount + sigLength]; + for (int i = 0; i < arrayCount; i++) { + result[i] = C_ARRAY; + } + System.arraycopy(typeSignature, 0, result, arrayCount, sigLength); + return result; +} +/** + * Creates a new type signature with the given amount of array nesting added + * to the given type signature. + * + * @param typeSignature the type signature + * @param arrayCount the desired number of levels of array nesting + * @return the encoded array type signature + */ +public static String createArraySignature(String typeSignature, int arrayCount) { + return new String(createArraySignature(typeSignature.toCharArray(), arrayCount)); +} + +/** + * Creates a new type signature from the given type name encoded as a character + * array. The type name may contain primitive types or array types or parameterized types. + * This method is equivalent to + * createTypeSignature(new String(typeName),isResolved).toCharArray(), + * although more efficient for callers with character arrays rather than strings. + * If the type name is qualified, then it is expected to be dot-based. + * + * @param typeName the possibly qualified type name + * @param isResolved true if the type name is to be considered + * resolved (for example, a type name from a binary class file), and + * false if the type name is to be considered unresolved + * (for example, a type name found in source code) + * @return the encoded type signature + * @see #createTypeSignature(java.lang.String,boolean) + * + * @since 2.0 + */ +public static char[] createCharArrayTypeSignature(char[] typeName, boolean isResolved) { + if (typeName == null) throw new IllegalArgumentException("null"); //$NON-NLS-1$ + int length = typeName.length; + if (length == 0) throw new IllegalArgumentException(new String(typeName)); + StringBuffer buffer = new StringBuffer(5); + int pos = encodeTypeSignature(typeName, 0, isResolved, length, buffer); + pos = consumeWhitespace(typeName, pos, length); + if (pos < length) throw new IllegalArgumentException(new String(typeName)); + char[] result = new char[length = buffer.length()]; + buffer.getChars(0, length, result, 0); + return result; +} + +/** + * Creates a new intersection type signature from the given type signatures. + * + *

The encoded type signature is dot-based.

+ * + * @param typeSignatures the given type signatures + * @return the encoded type signature + * @since 3.7.1 + */ +public static String createIntersectionTypeSignature(char[][] typeSignatures) { + StringBuffer buffer = new StringBuffer(); + buffer.append(Signature.C_INTERSECTION); + for (int i = 0, max = typeSignatures.length; i < max; i++) { + if (i > 0) { + buffer.append(Signature.C_COLON); + } + buffer.append(typeSignatures[i]); + } + return String.valueOf(buffer); +} + +/** + * Creates a new intersection type signature from the given type signatures. + * + *

The encoded type signature is dot-based.

+ * + * @param typeSignatures the given type signatures + * @return the encoded type signature + * @since 3.7.1 + */ +public static String createIntersectionTypeSignature(String[] typeSignatures) { + int typeSignaturesLenth = typeSignatures.length; + char[][] signatures = new char[typeSignaturesLenth][]; + for (int i = 0; i < typeSignaturesLenth; i++) { + signatures[i] = typeSignatures[i].toCharArray(); + } + return createIntersectionTypeSignature(signatures); +} +/** + * Creates a method signature from the given parameter and return type + * signatures. The encoded method signature is dot-based. + * + * @param parameterTypes the list of parameter type signatures + * @param returnType the return type signature + * @return the encoded method signature + * + * @since 2.0 + */ +public static char[] createMethodSignature(char[][] parameterTypes, char[] returnType) { + int parameterTypesLength = parameterTypes.length; + int parameterLength = 0; + for (int i = 0; i < parameterTypesLength; i++) { + parameterLength += parameterTypes[i].length; + + } + int returnTypeLength = returnType.length; + char[] result = new char[1 + parameterLength + 1 + returnTypeLength]; + result[0] = C_PARAM_START; + int index = 1; + for (int i = 0; i < parameterTypesLength; i++) { + char[] parameterType = parameterTypes[i]; + int length = parameterType.length; + System.arraycopy(parameterType, 0, result, index, length); + index += length; + } + result[index] = C_PARAM_END; + System.arraycopy(returnType, 0, result, index+1, returnTypeLength); + return result; +} +/** + * Creates a method signature from the given parameter and return type + * signatures. The encoded method signature is dot-based. This method + * is equivalent to + * createMethodSignature(parameterTypes, returnType). + * + * @param parameterTypes the list of parameter type signatures + * @param returnType the return type signature + * @return the encoded method signature + * @see Signature#createMethodSignature(char[][], char[]) + */ +public static String createMethodSignature(String[] parameterTypes, String returnType) { + int parameterTypesLenth = parameterTypes.length; + char[][] parameters = new char[parameterTypesLenth][]; + for (int i = 0; i < parameterTypesLenth; i++) { + parameters[i] = parameterTypes[i].toCharArray(); + } + return new String(createMethodSignature(parameters, returnType.toCharArray())); +} +/** + * Creates a new type parameter signature with the given name and bounds. + * + * @param typeParameterName the type parameter name + * @param boundSignatures the signatures of associated bounds or empty array if none + * @return the encoded type parameter signature + * + * @since 3.1 + */ +public static char[] createTypeParameterSignature(char[] typeParameterName, char[][] boundSignatures) { + int length = boundSignatures.length; + if (length == 0) { + return CharOperation.append(typeParameterName, C_COLON); // param signature with no bounds still gets trailing colon + } + int boundsSize = 0; + for (int i = 0; i < length; i++) { + boundsSize += boundSignatures[i].length + 1; + } + int nameLength = typeParameterName.length; + char[] result = new char[nameLength + boundsSize]; + System.arraycopy(typeParameterName, 0, result, 0, nameLength); + int index = nameLength; + for (int i = 0; i < length; i++) { + result[index++] = C_COLON; + int boundLength = boundSignatures[i].length; + System.arraycopy(boundSignatures[i], 0, result, index, boundLength); + index += boundLength; + } + return result; +} +/** + * Creates a new type parameter signature with the given name and bounds. + * + * @param typeParameterName the type parameter name + * @param boundSignatures the signatures of associated bounds or empty array if none + * @return the encoded type parameter signature + * + * @since 3.1 + */ +public static String createTypeParameterSignature(String typeParameterName, String[] boundSignatures) { + int length = boundSignatures.length; + char[][] boundSignatureChars = new char[length][]; + for (int i = 0; i < length; i++) { + boundSignatureChars[i] = boundSignatures[i].toCharArray(); + } + return new String(createTypeParameterSignature(typeParameterName.toCharArray(), boundSignatureChars)); +} + +/** + * Creates a new type signature from the given type name encoded as a character + * array. The type name may contain primitive types, array types or parameterized types. + * This method is equivalent to + * createTypeSignature(new String(typeName),isResolved), although + * more efficient for callers with character arrays rather than strings. If the + * type name is qualified, then it is expected to be dot-based. + * + * @param typeName the possibly qualified type name + * @param isResolved true if the type name is to be considered + * resolved (for example, a type name from a binary class file), and + * false if the type name is to be considered unresolved + * (for example, a type name found in source code) + * @return the encoded type signature + * @see #createTypeSignature(java.lang.String,boolean) + */ +public static String createTypeSignature(char[] typeName, boolean isResolved) { + return new String(createCharArrayTypeSignature(typeName, isResolved)); +} + +/** + * Creates a new type signature from the given type name. If the type name is qualified, + * then it is expected to be dot-based. The type name may contain primitive + * types or array types. However, parameterized types are not supported. + *

+ * For example: + *

+ * 
+ * createTypeSignature("int", hucairz) -> "I"
+ * createTypeSignature("java.lang.String", true) -> "Ljava.lang.String;"
+ * createTypeSignature("String", false) -> "QString;"
+ * createTypeSignature("java.lang.String", false) -> "Qjava.lang.String;"
+ * createTypeSignature("int []", false) -> "[I"
+ * 
+ * 
+ *

+ * + * @param typeName the possibly qualified type name + * @param isResolved true if the type name is to be considered + * resolved (for example, a type name from a binary class file), and + * false if the type name is to be considered unresolved + * (for example, a type name found in source code) + * @return the encoded type signature + */ +public static String createTypeSignature(String typeName, boolean isResolved) { + return createTypeSignature(typeName == null ? null : typeName.toCharArray(), isResolved); +} + +private static int encodeArrayDimension(char[] typeName, int pos, int length, StringBuffer buffer) { + int checkPos; + while (pos < length && (checkPos = checkNextChar(typeName, '[', pos, length, true)) > 0) { + pos = checkNextChar(typeName, ']', checkPos, length, false); + buffer.append(C_ARRAY); + } + return pos; +} + +private static int encodeQualifiedName(char[] typeName, int pos, int length, StringBuffer buffer) { + int count = 0; + char lastAppendedChar = 0; + nameLoop: while (pos < length) { + char currentChar = typeName[pos]; + switch (currentChar) { + case '<' : + case '>' : + case '[' : + case ',' : + break nameLoop; + case '.' : + buffer.append(C_DOT); + lastAppendedChar = C_DOT; + count++; + break; + default: + if (currentChar == ' ' || ScannerHelper.isWhitespace(currentChar)) { + if (lastAppendedChar == C_DOT) { // allow spaces after a dot + pos = consumeWhitespace(typeName, pos, length) - 1; // will be incremented + break; + } + // allow spaces before a dot + int checkPos = checkNextChar(typeName, '.', pos, length, true); + if (checkPos > 0) { + buffer.append(C_DOT); // process dot immediately to avoid one iteration + lastAppendedChar = C_DOT; + count++; + pos = checkPos; + break; + } + break nameLoop; + } + buffer.append(currentChar); + lastAppendedChar = currentChar; + count++; + break; + } + pos++; + } + if (count == 0) throw new IllegalArgumentException(new String(typeName)); + return pos; +} + +private static int encodeTypeSignature(char[] typeName, int start, boolean isResolved, int length, StringBuffer buffer) { + int pos = start; + pos = consumeWhitespace(typeName, pos, length); + if (pos >= length) throw new IllegalArgumentException(new String(typeName)); + int checkPos; + char currentChar = typeName[pos]; + switch (currentChar) { + // primitive type? + case 'b' : + checkPos = checkName(BOOLEAN, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_BOOLEAN); + return pos; + } + checkPos = checkName(BYTE, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_BYTE); + return pos; + } + break; + case 'd': + checkPos = checkName(DOUBLE, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_DOUBLE); + return pos; + } + break; + case 'f': + checkPos = checkName(FLOAT, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_FLOAT); + return pos; + } + break; + case 'i': + checkPos = checkName(INT, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_INT); + return pos; + } + break; + case 'l': + checkPos = checkName(LONG, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_LONG); + return pos; + } + break; + case 's': + checkPos = checkName(SHORT, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_SHORT); + return pos; + } + break; + case 'v': + checkPos = checkName(VOID, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_VOID); + return pos; + } + break; + case 'c': + checkPos = checkName(CHAR, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_CHAR); + return pos; + } else { + checkPos = checkName(CAPTURE, typeName, pos, length); + if (checkPos > 0) { + pos = consumeWhitespace(typeName, checkPos, length); + if (typeName[pos] != '?') { + break; + } + } else { + break; + } + } + buffer.append(C_CAPTURE); + //$FALL-THROUGH$ for wildcard part of capture typecheckPos + case '?': + // wildcard + pos = consumeWhitespace(typeName, pos+1, length); + checkPos = checkName(EXTENDS, typeName, pos, length); + if (checkPos > 0) { + buffer.append(C_EXTENDS); + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + return pos; + } + checkPos = checkName(SUPER, typeName, pos, length); + if (checkPos > 0) { + buffer.append(C_SUPER); + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + return pos; + } + buffer.append(C_STAR); + return pos; + } + // non primitive type + checkPos = checkArrayDimension(typeName, pos, length); + int end; + if (checkPos > 0) { + end = encodeArrayDimension(typeName, checkPos, length, buffer); + } else { + end = -1; + } + buffer.append(isResolved ? C_RESOLVED : C_UNRESOLVED); + while (true) { // loop on qualifiedName[][.qualifiedName[]* + pos = encodeQualifiedName(typeName, pos, length, buffer); + checkPos = checkNextChar(typeName, '<', pos, length, true); + if (checkPos > 0) { + buffer.append(C_GENERIC_START); + // Stop gap fix for <>. + if ((pos = checkNextChar(typeName, '>', checkPos, length, true)) > 0) { + buffer.append(C_GENERIC_END); + } else { + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + while ((checkPos = checkNextChar(typeName, ',', pos, length, true)) > 0) { + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + } + pos = checkNextChar(typeName, '>', pos, length, false); + buffer.append(C_GENERIC_END); + } + } + checkPos = checkNextChar(typeName, '.', pos, length, true); + if (checkPos > 0) { + buffer.append(C_DOT); + pos = checkPos; + } else { + break; + } + } + buffer.append(C_NAME_END); + if (end > 0) pos = end; // skip array dimension which were preprocessed + return pos; +} + +/** + * Returns the array count (array nesting depth) of the given type signature. + * + * @param typeSignature the type signature + * @return the array nesting depth, or 0 if not an array + * @exception IllegalArgumentException if the signature is not syntactically + * correct + * + * @since 2.0 + */ +public static int getArrayCount(char[] typeSignature) throws IllegalArgumentException { + try { + int count = 0; + while (typeSignature[count] == C_ARRAY) { + ++count; + } + return count; + } catch (ArrayIndexOutOfBoundsException e) { // signature is syntactically incorrect if last character is C_ARRAY + throw new IllegalArgumentException(); + } +} + +/** + * Returns the array count (array nesting depth) of the given type signature. + * + * @param typeSignature the type signature + * @return the array nesting depth, or 0 if not an array + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static int getArrayCount(String typeSignature) throws IllegalArgumentException { + return getArrayCount(typeSignature.toCharArray()); +} + +/** + * Returns the type signature without any array nesting. + *

+ * For example: + *

+ * 
+ * getElementType({'[', '[', 'I'}) --> {'I'}.
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type signature without arrays + * @exception IllegalArgumentException if the signature is not syntactically + * correct + * + * @since 2.0 + */ +public static char[] getElementType(char[] typeSignature) throws IllegalArgumentException { + int count = getArrayCount(typeSignature); + if (count == 0) return typeSignature; + int length = typeSignature.length; + char[] result = new char[length-count]; + System.arraycopy(typeSignature, count, result, 0, length-count); + return result; +} + +/** + * Returns the type signature without any array nesting. + *

+ * For example: + *

+ * 
+ * getElementType("[[I") --> "I".
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type signature without arrays + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static String getElementType(String typeSignature) throws IllegalArgumentException { + char[] signature = typeSignature.toCharArray(); + char[] elementType = getElementType(signature); + return signature == elementType ? typeSignature : new String(elementType); +} +/** + * Extracts the type bounds' signatures from the given intersection type signature. + * Returns an empty array if the type signature is not an intersection type signature. + * + * @param intersectionTypeSignature the intersection type signature + * @return the signatures of the type bounds + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.7.1 + */ +public static char[][] getIntersectionTypeBounds(char[] intersectionTypeSignature) throws IllegalArgumentException { + if (getTypeSignatureKind(intersectionTypeSignature) != INTERSECTION_TYPE_SIGNATURE) { + return CharOperation.NO_CHAR_CHAR; + } + ArrayList args = new ArrayList(); + int i = 1; // skip the '|' + int length = intersectionTypeSignature.length; + for (;;) { + int e = Util.scanClassTypeSignature(intersectionTypeSignature, i); + if (e < 0) { + throw new IllegalArgumentException("Invalid format"); //$NON-NLS-1$ + } + args.add(CharOperation.subarray(intersectionTypeSignature, i, e + 1)); + if (e == length - 1) { + int size = args.size(); + char[][] result = new char[size][]; + args.toArray(result); + return result; + } else if (intersectionTypeSignature[e + 1] != C_COLON) { + throw new IllegalArgumentException("Invalid format"); //$NON-NLS-1$ + } + i = e + 2; // add one to skip C_COLON + } +} +/** + * Extracts the type bounds' signatures from the given intersection type signature. + * Returns an empty array if the type signature is not an intersection type signature. + * + * @param intersectionTypeSignature the intersection type signature + * @return the signatures of the type bounds + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.7.1 + */ +public static String[] getIntersectionTypeBounds(String intersectionTypeSignature) throws IllegalArgumentException { + char[][] args = getIntersectionTypeBounds(intersectionTypeSignature.toCharArray()); + return CharOperation.toStrings(args); +} +/** + * Returns the number of parameter types in the given method signature. + * + * @param methodSignature the method signature + * @return the number of parameters + * @exception IllegalArgumentException if the signature is not syntactically + * correct + * @since 2.0 + */ +public static int getParameterCount(char[] methodSignature) throws IllegalArgumentException { + try { + int count = 0; + int i = CharOperation.indexOf(C_PARAM_START, methodSignature); + if (i < 0) { + throw new IllegalArgumentException(); + } else { + i++; + } + for (;;) { + if (methodSignature[i] == C_PARAM_END) { + return count; + } + int e= Util.scanTypeSignature(methodSignature, i); + if (e < 0) { + throw new IllegalArgumentException(); + } else { + i = e + 1; + } + count++; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } +} + +/** + * Returns the number of parameter types in the given method signature. + * + * @param methodSignature the method signature + * @return the number of parameters + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static int getParameterCount(String methodSignature) throws IllegalArgumentException { + return getParameterCount(methodSignature.toCharArray()); +} + +/** + * Extracts the parameter type signatures from the given method signature. + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of parameter type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 2.0 + */ +public static char[][] getParameterTypes(char[] methodSignature) throws IllegalArgumentException { + try { + int count = getParameterCount(methodSignature); + char[][] result = new char[count][]; + if (count == 0) { + return result; + } + int i = CharOperation.indexOf(C_PARAM_START, methodSignature); + if (i < 0) { + throw new IllegalArgumentException(); + } else { + i++; + } + int t = 0; + for (;;) { + if (methodSignature[i] == C_PARAM_END) { + return result; + } + int e = Util.scanTypeSignature(methodSignature, i); + if (e < 0) { + throw new IllegalArgumentException(); + } + result[t] = CharOperation.subarray(methodSignature, i, e + 1); + t++; + i = e + 1; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } +} +/** + * Extracts the parameter type signatures from the given method signature. + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of parameter type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + */ +public static String[] getParameterTypes(String methodSignature) throws IllegalArgumentException { + char[][] parameterTypes = getParameterTypes(methodSignature.toCharArray()); + return CharOperation.toStrings(parameterTypes); +} + +/** + * Returns a char array containing all but the last segment of the given + * dot-separated qualified name. Returns the empty char array if it is not qualified. + *

+ * For example: + *

+ * 
+ * getQualifier({'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}) -> {'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g'}
+ * getQualifier({'O', 'u', 't', 'e', 'r', '.', 'I', 'n', 'n', 'e', 'r'}) -> {'O', 'u', 't', 'e', 'r'}
+ * getQualifier({'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'L', 'i', 's', 't', '<', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', '>'}) -> {'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l'}
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the qualifier prefix, or the empty char array if the name contains no + * dots + * @exception NullPointerException if name is null + * @since 2.0 + */ +public static char[] getQualifier(char[] name) { + int firstGenericStart = CharOperation.indexOf(C_GENERIC_START, name); + int lastDot = CharOperation.lastIndexOf(C_DOT, name, 0, firstGenericStart == -1 ? name.length-1 : firstGenericStart); + if (lastDot == -1) { + return CharOperation.NO_CHAR; + } + return CharOperation.subarray(name, 0, lastDot); +} + +/** + * Returns a string containing all but the last segment of the given + * dot-separated qualified name. Returns the empty string if it is not qualified. + *

+ * For example: + *

+ * 
+ * getQualifier("java.lang.Object") -> "java.lang"
+ * getQualifier("Outer.Inner") -> "Outer"
+ * getQualifier("java.util.List<java.lang.String>") -> "java.util"
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the qualifier prefix, or the empty string if the name contains no + * dots + * @exception NullPointerException if name is null + */ +public static String getQualifier(String name) { + char[] qualifier = getQualifier(name.toCharArray()); + if (qualifier.length == 0) return org.eclipse.jdt.internal.compiler.util.Util.EMPTY_STRING; + return new String(qualifier); +} + +/** + * Extracts the return type from the given method signature. The method signature is + * expected to be dot-based. + * + * @param methodSignature the method signature + * @return the type signature of the return type + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 2.0 + */ +public static char[] getReturnType(char[] methodSignature) throws IllegalArgumentException { + // skip type parameters + int paren = CharOperation.lastIndexOf(C_PARAM_END, methodSignature); + if (paren == -1) { + throw new IllegalArgumentException(); + } + // there could be thrown exceptions behind, thus scan one type exactly + int last = Util.scanTypeSignature(methodSignature, paren+1); + return CharOperation.subarray(methodSignature, paren + 1, last+1); +} + +/** + * Extracts the return type from the given method signature. The method signature is + * expected to be dot-based. + * + * @param methodSignature the method signature + * @return the type signature of the return type + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + */ +public static String getReturnType(String methodSignature) throws IllegalArgumentException { + return new String(getReturnType(methodSignature.toCharArray())); +} + +/** + * Returns package fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureQualifier({'L', 'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'M', 'a', 'p', '$', 'E', 'n', 't', 'r', 'y', ';'}) -> {'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l'}
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the package fragment (separators are '.') + * @since 3.1 + */ +public static char[] getSignatureQualifier(char[] typeSignature) { + if(typeSignature == null) return CharOperation.NO_CHAR; + + char[] qualifiedType = Signature.toCharArray(typeSignature); + + int dotCount = 0; + indexFound: for(int i = 0; i < typeSignature.length; i++) { + switch(typeSignature[i]) { + case C_DOT: + dotCount++; + break; + case C_GENERIC_START: + break indexFound; + case C_DOLLAR: + break indexFound; + } + } + + if(dotCount > 0) { + for(int i = 0; i < qualifiedType.length; i++) { + if(qualifiedType[i] == '.') { + dotCount--; + } + if(dotCount <= 0) { + return CharOperation.subarray(qualifiedType, 0, i); + } + } + } + return CharOperation.NO_CHAR; +} +/** + * Returns package fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureQualifier("Ljava.util.Map$Entry") -> "java.util"
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the package fragment (separators are '.') + * @since 3.1 + */ +public static String getSignatureQualifier(String typeSignature) { + return new String(getSignatureQualifier(typeSignature == null ? null : typeSignature.toCharArray())); +} +/** + * Returns type fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureSimpleName({'L', 'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'M', 'a', 'p', '$', 'E', 'n', 't', 'r', 'y', ';'}) -> {'M', 'a', 'p', '.', 'E', 'n', 't', 'r', 'y'}
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type fragment (separators are '.') + * @since 3.1 + */ +public static char[] getSignatureSimpleName(char[] typeSignature) { + if(typeSignature == null) return CharOperation.NO_CHAR; + + char[] qualifiedType = Signature.toCharArray(typeSignature); + + int dotCount = 0; + indexFound: for(int i = 0; i < typeSignature.length; i++) { + switch(typeSignature[i]) { + case C_DOT: + dotCount++; + break; + case C_GENERIC_START: + break indexFound; + case C_DOLLAR: + break indexFound; + } + } + + if(dotCount > 0) { + int typeStart = 0; + for(int i = 0; i < qualifiedType.length; i++) { + switch (qualifiedType[i]) { + case '.': + dotCount--; + break; + case ' ': + typeStart = i+1; + break; + } + if(dotCount <= 0) { + char[] simpleName = CharOperation.subarray(qualifiedType, i + 1, qualifiedType.length); + if (typeStart > 0 && typeStart < qualifiedType.length) + return CharOperation.concat(CharOperation.subarray(qualifiedType, 0, typeStart), simpleName); + return simpleName; + } + } + } + return qualifiedType; +} +/** + * Returns type fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureSimpleName("Ljava.util.Map$Entry") -> "Map.Entry"
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type fragment (separators are '.') + * @since 3.1 + */ +public static String getSignatureSimpleName(String typeSignature) { + return new String(getSignatureSimpleName(typeSignature == null ? null : typeSignature.toCharArray())); +} +/** + * Returns the last segment of the given dot-separated qualified name. + * Returns the given name if it is not qualified. + *

+ * For example: + *

+ * 
+ * getSimpleName({'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}) -> {'O', 'b', 'j', 'e', 'c', 't'}
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the last segment of the qualified name + * @exception NullPointerException if name is null + * @since 2.0 + */ +public static char[] getSimpleName(char[] name) { + + int lastDot = -1, lastGenericStart = -1, lastGenericEnd = -1; + int depth = 0; + int length = name.length; + lastDotLookup: for (int i = length -1; i >= 0; i--) { + switch (name[i]) { + case '.': + if (depth == 0) { + lastDot = i; + break lastDotLookup; + } + break; + case '<': + depth--; + if (depth == 0) lastGenericStart = i; + break; + case '>': + if (depth == 0) lastGenericEnd = i; + depth++; + break; + } + } + if (lastGenericStart < 0) { + if (lastDot < 0) { + return name; + } + return CharOperation.subarray(name, lastDot + 1, length); + } + StringBuffer buffer = new StringBuffer(10); + int nameStart = lastDot < 0 ? 0 : lastDot+1; + buffer.append(name, nameStart, lastGenericStart - nameStart); + appendArgumentSimpleNames(name, lastGenericStart, lastGenericEnd, buffer); + buffer.append(name, lastGenericEnd+1, length-lastGenericEnd-1); // copy trailing portion, may contain dimensions + char[] result = new char[length = buffer.length()]; + buffer.getChars(0, length, result, 0); + return result; +} +/** + * Returns the last segment of the given dot-separated qualified name. + * Returns the given name if it is not qualified. + *

+ * For example: + *

+ * 
+ * getSimpleName("java.lang.Object") -> "Object"
+ * 
+ * 
+ * getSimpleName("java.util.Map<java.lang.String, java.lang.Object>") -> "Map<String,Object>"
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the last segment of the qualified name + * @exception NullPointerException if name is null + */ +public static String getSimpleName(String name) { + int lastDot = -1, lastGenericStart = -1, lastGenericEnd = -1; + int depth = 0; + int length = name.length(); + lastDotLookup: for (int i = length -1; i >= 0; i--) { + switch (name.charAt(i)) { + case '.': + if (depth == 0) { + lastDot = i; + break lastDotLookup; + } + break; + case '<': + depth--; + if (depth == 0) lastGenericStart = i; + break; + case '>': + if (depth == 0) lastGenericEnd = i; + depth++; + break; + } + } + if (lastGenericStart < 0) { + if (lastDot < 0) { + return name; + } + return name.substring(lastDot + 1, length); + } + StringBuffer buffer = new StringBuffer(10); + char[] nameChars = name.toCharArray(); + int nameStart = lastDot < 0 ? 0 : lastDot+1; + buffer.append(nameChars, nameStart, lastGenericStart - nameStart); + appendArgumentSimpleNames(nameChars, lastGenericStart, lastGenericEnd, buffer); + buffer.append(nameChars, lastGenericEnd+1, length-lastGenericEnd-1); // copy trailing portion, may contain dimensions + return buffer.toString(); +} +/** + * Returns all segments of the given dot-separated qualified name. + * Returns an array with only the given name if it is not qualified. + * Returns an empty array if the name is empty. + *

+ * For example: + *

+ * 
+ * getSimpleNames({'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}) -> {{'j', 'a', 'v', 'a'}, {'l', 'a', 'n', 'g'}, {'O', 'b', 'j', 'e', 'c', 't'}}
+ * getSimpleNames({'O', 'b', 'j', 'e', 'c', 't'}) -> {{'O', 'b', 'j', 'e', 'c', 't'}}
+ * getSimpleNames({}) -> {}
+ * getSimpleNames({'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'L', 'i', 's', 't', '<', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', '>'}) -> {{'j', 'a', 'v', 'a'}, {'l', 'a', 'n', 'g'}, {'L', 'i', 's', 't', '<', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g'}}
+ * 
+ * 
+ * + * @param name the name + * @return the list of simple names, possibly empty + * @exception NullPointerException if name is null + * @since 2.0 + */ +public static char[][] getSimpleNames(char[] name) { + int length = name == null ? 0 : name.length; + if (length == 0) + return CharOperation.NO_CHAR_CHAR; + + int wordCount = 1; + countingWords: for (int i = 0; i < length; i++) + switch(name[i]) { + case C_DOT: + wordCount++; + break; + case C_GENERIC_START: + break countingWords; + } + char[][] split = new char[wordCount][]; + int last = 0, currentWord = 0; + for (int i = 0; i < length; i++) { + if (name[i] == C_GENERIC_START) break; + if (name[i] == C_DOT) { + split[currentWord] = new char[i - last]; + System.arraycopy( + name, + last, + split[currentWord++], + 0, + i - last); + last = i + 1; + } + } + split[currentWord] = new char[length - last]; + System.arraycopy(name, last, split[currentWord], 0, length - last); + return split; +} +/** + * Returns all segments of the given dot-separated qualified name. + * Returns an array with only the given name if it is not qualified. + * Returns an empty array if the name is empty. + *

+ * For example: + *

+ * 
+ * getSimpleNames("java.lang.Object") -> {"java", "lang", "Object"}
+ * getSimpleNames("Object") -> {"Object"}
+ * getSimpleNames("") -> {}
+ * getSimpleNames("java.util.List<java.lang.String>") ->
+ *   {"java", "util", "List<java.lang.String>"}
+ * 
+ * 
+ * + * @param name the name + * @return the list of simple names, possibly empty + * @exception NullPointerException if name is null + */ +public static String[] getSimpleNames(String name) { + return CharOperation.toStrings(getSimpleNames(name.toCharArray())); +} + +/** + * Extracts the thrown exception type signatures from the given method signature if any + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of thrown exception type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static char[][] getThrownExceptionTypes(char[] methodSignature) throws IllegalArgumentException { + // skip type parameters + int exceptionStart = CharOperation.indexOf(C_EXCEPTION_START, methodSignature); + if (exceptionStart == -1) { + int paren = CharOperation.lastIndexOf(C_PARAM_END, methodSignature); + if (paren == -1) { + throw new IllegalArgumentException(); + } + // ignore return type + exceptionStart = Util.scanTypeSignature(methodSignature, paren+1) + 1; + int length = methodSignature.length; + if (exceptionStart == length) return CharOperation.NO_CHAR_CHAR; + throw new IllegalArgumentException(); + } + int length = methodSignature.length; + int i = exceptionStart; + ArrayList exceptionList = new ArrayList(1); + while (i < length) { + if (methodSignature[i] == C_EXCEPTION_START) { + exceptionStart++; + i++; + } else { + throw new IllegalArgumentException(); + } + i = Util.scanTypeSignature(methodSignature, i) + 1; + exceptionList.add(CharOperation.subarray(methodSignature, exceptionStart,i)); + exceptionStart = i; + } + char[][] result; + exceptionList.toArray(result = new char[exceptionList.size()][]); + return result; +} +/** + * Extracts the thrown exception type signatures from the given method signature if any + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of thrown exception type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static String[] getThrownExceptionTypes(String methodSignature) throws IllegalArgumentException { + char[][] parameterTypes = getThrownExceptionTypes(methodSignature.toCharArray()); + return CharOperation.toStrings(parameterTypes); +} +/** + * Extracts the type argument signatures from the given type signature. + * Returns an empty array if the type signature is not a parameterized type signature. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signatures of the type arguments + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.1 + */ +public static char[][] getTypeArguments(char[] parameterizedTypeSignature) throws IllegalArgumentException { + int length = parameterizedTypeSignature.length; + if (length < 2 || parameterizedTypeSignature[length-2] != C_GENERIC_END) + // cannot have type arguments otherwise signature would end by ">;" + return CharOperation.NO_CHAR_CHAR; + int count = 1; // start to count generic end/start peers + int start = length - 2; + while (start >= 0 && count > 0) { + switch (parameterizedTypeSignature[--start]) { + case C_GENERIC_START: + count--; + break; + case C_GENERIC_END: + count++; + break; + } + } + if (start < 0) // invalid number of generic start/end + throw new IllegalArgumentException(); + ArrayList args = new ArrayList(); + int p = start + 1; + while (true) { + if (p >= parameterizedTypeSignature.length) { + throw new IllegalArgumentException(); + } + char c = parameterizedTypeSignature[p]; + if (c == C_GENERIC_END) { + int size = args.size(); + char[][] result = new char[size][]; + args.toArray(result); + return result; + } + int e = Util.scanTypeArgumentSignature(parameterizedTypeSignature, p); + args.add(CharOperation.subarray(parameterizedTypeSignature, p, e+1)); + p = e + 1; + } +} +/** + * Extracts the type argument signatures from the given type signature. + * Returns an empty array if the type signature is not a parameterized type signature. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signatures of the type arguments + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.1 + */ +public static String[] getTypeArguments(String parameterizedTypeSignature) throws IllegalArgumentException { + char[][] args = getTypeArguments(parameterizedTypeSignature.toCharArray()); + return CharOperation.toStrings(args); +} +/** + * Extracts the type erasure signature from the given parameterized type signature. + * Returns the given type signature if it is not parameterized. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signature of the type erasure + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static char[] getTypeErasure(char[] parameterizedTypeSignature) throws IllegalArgumentException { + int end = CharOperation.indexOf(C_GENERIC_START, parameterizedTypeSignature); + if (end == -1) return parameterizedTypeSignature; + int length = parameterizedTypeSignature.length; + char[] result = new char[length]; + int pos = 0; + int start = 0; + int deep= 0; + for (int idx=end; idx 0) throw new IllegalArgumentException(); + int size = pos+length-start; + char[] resized = new char[size]; + System.arraycopy(result, 0, resized, 0, pos); + System.arraycopy(parameterizedTypeSignature, start, resized, pos, length-start); + return resized; +} +/** + * Extracts the type erasure signature from the given parameterized type signature. + * Returns the given type signature if it is not parameterized. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signature of the type erasure + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static String getTypeErasure(String parameterizedTypeSignature) throws IllegalArgumentException { + char[] signature = parameterizedTypeSignature.toCharArray(); + char[] erasure = getTypeErasure(signature); + return signature == erasure ? parameterizedTypeSignature : new String(erasure); +} + +/** + * Extracts the class and interface bounds from the given formal type + * parameter signature. The class bound, if present, is listed before + * the interface bounds. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the (possibly empty) list of type signatures for the bounds + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static char[][] getTypeParameterBounds(char[] formalTypeParameterSignature) throws IllegalArgumentException { + int p1 = CharOperation.indexOf(C_COLON, formalTypeParameterSignature); + if (p1 < 0) { + // no ":" means can't be a formal type parameter signature + throw new IllegalArgumentException(); + } + if (p1 == formalTypeParameterSignature.length - 1) { + // no class or interface bounds + return CharOperation.NO_CHAR_CHAR; + } + int p2 = CharOperation.indexOf(C_COLON, formalTypeParameterSignature, p1 + 1); + char[] classBound; + if (p2 < 0) { + // no interface bounds + classBound = CharOperation.subarray(formalTypeParameterSignature, p1 + 1, formalTypeParameterSignature.length); + return new char[][] {classBound}; + } + if (p2 == p1 + 1) { + // no class bound, but 1 or more interface bounds + classBound = null; + } else { + classBound = CharOperation.subarray(formalTypeParameterSignature, p1 + 1, p2); + } + char[][] interfaceBounds = CharOperation.splitOn(C_COLON, formalTypeParameterSignature, p2 + 1, formalTypeParameterSignature.length); + if (classBound == null) { + return interfaceBounds; + } + int resultLength = interfaceBounds.length + 1; + char[][] result = new char[resultLength][]; + result[0] = classBound; + System.arraycopy(interfaceBounds, 0, result, 1, interfaceBounds.length); + return result; +} + +/** + * Extracts the class and interface bounds from the given formal type + * parameter signature. The class bound, if present, is listed before + * the interface bounds. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the (possibly empty) list of type signatures for the bounds + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static String[] getTypeParameterBounds(String formalTypeParameterSignature) throws IllegalArgumentException { + char[][] bounds = getTypeParameterBounds(formalTypeParameterSignature.toCharArray()); + return CharOperation.toStrings(bounds); +} + +/** + * Extracts the type parameter signatures from the given method or type signature. + * The method or type signature is expected to be dot-based. + * + * @param methodOrTypeSignature the method or type signature + * @return the list of type parameter signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static char[][] getTypeParameters(char[] methodOrTypeSignature) throws IllegalArgumentException { + try { + int length = methodOrTypeSignature.length; + if (length == 0) return CharOperation.NO_CHAR_CHAR; + if (methodOrTypeSignature[0] != C_GENERIC_START) return CharOperation.NO_CHAR_CHAR; + + ArrayList paramList = new ArrayList(1); + int paramStart = 1, i = 1; // start after leading '<' + while (i < length) { + if (methodOrTypeSignature[i] == C_GENERIC_END) { + int size = paramList.size(); + if (size == 0) throw new IllegalArgumentException(); + char[][] result; + paramList.toArray(result = new char[size][]); + return result; + } + i = CharOperation.indexOf(C_COLON, methodOrTypeSignature, i); + if (i < 0 || i >= length) + throw new IllegalArgumentException(); + // iterate over bounds + while (methodOrTypeSignature[i] == ':') { + i++; // skip colon + switch (methodOrTypeSignature[i]) { + case ':': + // no class bound + break; + case C_GENERIC_END: + break; + case C_RESOLVED: + try { + i = Util.scanClassTypeSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not a class type signature -> it is a new type parameter + } + break; + case C_ARRAY: + try { + i = Util.scanArrayTypeSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not an array type signature -> it is a new type parameter + } + break; + case C_TYPE_VARIABLE: + try { + i = Util.scanTypeVariableSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not a type variable signature -> it is a new type parameter + } + break; + case C_CAPTURE: + try { + i = Util.scanCaptureTypeSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not a capture variable signature -> it is a new type parameter + } + break; + // default: another type parameter is starting + } + } + paramList.add(CharOperation.subarray(methodOrTypeSignature, paramStart, i)); + paramStart = i; // next param start from here + } + } catch (ArrayIndexOutOfBoundsException e) { + // invalid signature, fall through + } + throw new IllegalArgumentException(); +} +/** + * Extracts the type parameter signatures from the given method or type signature. + * The method or type signature is expected to be dot-based. + * + * @param methodOrTypeSignature the method or type signature + * @return the list of type parameter signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static String[] getTypeParameters(String methodOrTypeSignature) throws IllegalArgumentException { + char[][] params = getTypeParameters(methodOrTypeSignature.toCharArray()); + return CharOperation.toStrings(params); +} +/** + * Returns the kind of type signature encoded by the given string. + * + * @param typeSignature the type signature string + * @return the kind of type signature; one of the kind constants: + * {@link #ARRAY_TYPE_SIGNATURE}, {@link #CLASS_TYPE_SIGNATURE}, + * {@link #BASE_TYPE_SIGNATURE}, or {@link #TYPE_VARIABLE_SIGNATURE}, + * or (since 3.1) {@link #WILDCARD_TYPE_SIGNATURE} or {@link #CAPTURE_TYPE_SIGNATURE}, + * or (since 3.7) {@link #INTERSECTION_TYPE_SIGNATURE} + * @exception IllegalArgumentException if this is not a type signature + * @since 3.0 + */ +public static int getTypeSignatureKind(char[] typeSignature) { + // need a minimum 1 char + if (typeSignature.length < 1) { + throw new IllegalArgumentException(); + } + char c = typeSignature[0]; + if (c == C_GENERIC_START) { + int count = 1; + for (int i = 1, length = typeSignature.length; i < length; i++) { + switch (typeSignature[i]) { + case C_GENERIC_START: + count++; + break; + case C_GENERIC_END: + count--; + break; + } + if (count == 0) { + if (i+1 < length) + c = typeSignature[i+1]; + break; + } + } + } + switch (c) { + case C_ARRAY : + return ARRAY_TYPE_SIGNATURE; + case C_RESOLVED : + case C_UNRESOLVED : + return CLASS_TYPE_SIGNATURE; + case C_TYPE_VARIABLE : + return TYPE_VARIABLE_SIGNATURE; + case C_BOOLEAN : + case C_BYTE : + case C_CHAR : + case C_DOUBLE : + case C_FLOAT : + case C_INT : + case C_LONG : + case C_SHORT : + case C_VOID : + return BASE_TYPE_SIGNATURE; + case C_STAR : + case C_SUPER : + case C_EXTENDS : + return WILDCARD_TYPE_SIGNATURE; + case C_CAPTURE : + return CAPTURE_TYPE_SIGNATURE; + case C_INTERSECTION : + return INTERSECTION_TYPE_SIGNATURE; + default : + throw new IllegalArgumentException(); + } +} + +/** + * Returns the kind of type signature encoded by the given string. + * + * @param typeSignature the type signature string + * @return the kind of type signature; one of the kind constants: + * {@link #ARRAY_TYPE_SIGNATURE}, {@link #CLASS_TYPE_SIGNATURE}, + * {@link #BASE_TYPE_SIGNATURE}, or {@link #TYPE_VARIABLE_SIGNATURE}, + * or (since 3.1) {@link #WILDCARD_TYPE_SIGNATURE} or {@link #CAPTURE_TYPE_SIGNATURE} + * or (since 3.7) {@link #INTERSECTION_TYPE_SIGNATURE} + * @exception IllegalArgumentException if this is not a type signature + * @since 3.0 + */ +public static int getTypeSignatureKind(String typeSignature) { + return getTypeSignatureKind(typeSignature.toCharArray()); +} +/** + * Extracts the type variable name from the given formal type parameter + * signature. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the name of the type variable + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static char[] getTypeVariable(char[] formalTypeParameterSignature) throws IllegalArgumentException { + int p = CharOperation.indexOf(C_COLON, formalTypeParameterSignature); + if (p < 0) { + // no ":" means can't be a formal type parameter signature + throw new IllegalArgumentException(); + } + return CharOperation.subarray(formalTypeParameterSignature, 0, p); +} +/** + * Extracts the type variable name from the given formal type parameter + * signature. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the name of the type variable + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static String getTypeVariable(String formalTypeParameterSignature) throws IllegalArgumentException { + return new String(getTypeVariable(formalTypeParameterSignature.toCharArray())); +} +/** + * Removes any capture information from the given type or method signature + * and returns the resulting signature. + * Returns the type or method signature itself if no capture information is + * present. + *

+ * For example (using equivalent string-based method): + *

+ * 
+ * removeCapture("LTest<!+Ljava.lang.Throwable;>;")
+ * will return: "LTest<+Ljava.lang.Throwable;>;"
+ * 
+ * 
+ *

+ * + * @param methodOrTypeSignature the signature which may have been captured + * @return a new signature without capture information or the signature itself + * if no specific capture information is present + * @exception NullPointerException if methodOrTypeSignature is null + * + * @since 3.1 + */ +public static char[] removeCapture(char[] methodOrTypeSignature) { + return CharOperation.remove(methodOrTypeSignature, C_CAPTURE); +} +/** + * Removes any capture information from the given type or method signature + * and returns the resulting signature. + * Returns the type or method signature itself if no capture information is + * present. + *

+ * For example: + *

+ * 
+ * removeCapture("LTest<!+Ljava.lang.Throwable;>;")
+ * will return: "LTest<+Ljava.lang.Throwable;>;"
+ * 
+ * 
+ *

+ * + * @param methodOrTypeSignature the signature which may have been captured + * @return a new signature without capture information or the signature itself + * if no specific capture information is present + * @exception NullPointerException if methodOrTypeSignature is null + * + * @since 3.1 + */ +public static String removeCapture(String methodOrTypeSignature) { + char[] array = methodOrTypeSignature.toCharArray(); + char[] result = removeCapture(array); + if (array == result) return methodOrTypeSignature; + return new String(result); +} +/** + * Converts the given type signature to a readable string. The signature is expected to + * be dot-based. + * + *

+ * For example: + *

+ * 
+ * toString({'[', 'L', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', ';'}) -> {'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', '[', ']'}
+ * toString({'I'}) -> {'i', 'n', 't'}
+ * toString({'+', 'L', 'O', 'b', 'j', 'e', 'c', 't', ';'}) -> {'?', ' ', 'e', 'x', 't', 'e', 'n', 'd', 's', ' ', 'O', 'b', 'j', 'e', 'c', 't'}
+ * 
+ * 
+ *

+ *

+ * Note: This method assumes that a type signature containing a '$' + * is an inner type signature. While this is correct in most cases, someone could + * define a non-inner type name containing a '$'. Handling this + * correctly in all cases would have required resolving the signature, which + * generally not feasible. + *

+ * + * @param signature the type signature + * @return the string representation of the type + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 2.0 + */ +public static char[] toCharArray(char[] signature) throws IllegalArgumentException { + int sigLength = signature.length; + if (sigLength == 0) { + throw new IllegalArgumentException(); + } + if (signature[0] == C_PARAM_START || signature[0] == C_GENERIC_START) { + return toCharArray(signature, CharOperation.NO_CHAR, null, true, true); + } + + StringBuffer buffer = new StringBuffer(signature.length + 10); + appendTypeSignature(signature, 0, true, buffer); + char[] result = new char[buffer.length()]; + buffer.getChars(0, buffer.length(), result, 0); + return result; +} +/** + * Converts the given method signature to a readable form. The method signature is expected to + * be dot-based. + *

+ * For example: + *

+ * 
+ * toString("([Ljava.lang.String;)V", "main", new String[] {"args"}, false, true) -> "void main(String[] args)"
+ * 
+ * 
+ *

+ * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @return the char array representation of the method signature + * @throws IllegalArgumentException if the method signature is syntactically incorrect + * @since 2.0 + */ +public static char[] toCharArray(char[] methodSignature, char[] methodName, char[][] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType) { + return toCharArray(methodSignature, methodName, parameterNames, fullyQualifyTypeNames, includeReturnType, false); +} + +/** + * Converts the given method signature to a readable form. The method signature is expected to + * be dot-based. + *

+ * For example: + *

+ * 
+ * toString("([Ljava.lang.String;)V", "main", new String[] {"args"}, false, true) -> "void main(String[] args)"
+ * 
+ * 
+ *

+ * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @param isVargArgs true if the last argument should be displayed as a + * variable argument, false otherwise. + * @return the char array representation of the method signature + * @throws IllegalArgumentException if the method signature is syntactically incorrect + * + * @since 3.1 + */ +public static char[] toCharArray(char[] methodSignature, char[] methodName, char[][] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType, boolean isVargArgs) { + int firstParen = CharOperation.indexOf(C_PARAM_START, methodSignature); + if (firstParen == -1) { + throw new IllegalArgumentException(); + } + + StringBuffer buffer = new StringBuffer(methodSignature.length + 10); + + // return type + if (includeReturnType) { + char[] rts = getReturnType(methodSignature); + appendTypeSignature(rts, 0 , fullyQualifyTypeNames, buffer); + buffer.append(' '); + } + + // selector + if (methodName != null) { + buffer.append(methodName); + } + + // parameters + buffer.append('('); + char[][] pts = getParameterTypes(methodSignature); + // search for the last array in the signature + int max = pts.length; + int index = max - 1; + loop: for (int i = index; i >= 0; i--) { + if (pts[i][0] == Signature.C_ARRAY) { + break loop; + } + index--; + } + for (int i = 0; i < max; i++) { + if (i == index) { + appendTypeSignature(pts[i], 0 , fullyQualifyTypeNames, buffer, isVargArgs); + } else { + appendTypeSignature(pts[i], 0 , fullyQualifyTypeNames, buffer); + } + if (parameterNames != null) { + buffer.append(' '); + buffer.append(parameterNames[i]); + } + if (i != pts.length - 1) { + buffer.append(','); + buffer.append(' '); + } + } + buffer.append(')'); + char[] result = new char[buffer.length()]; + buffer.getChars(0, buffer.length(), result, 0); + return result; +} + +/** + * Converts the given array of qualified name segments to a qualified name. + *

+ * For example: + *

+ * 
+ * toQualifiedName({{'j', 'a', 'v', 'a'}, {'l', 'a', 'n', 'g'}, {'O', 'b', 'j', 'e', 'c', 't'}}) -> {'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}
+ * toQualifiedName({{'O', 'b', 'j', 'e', 'c', 't'}}) -> {'O', 'b', 'j', 'e', 'c', 't'}
+ * toQualifiedName({{}}) -> {}
+ * 
+ * 
+ *

+ * + * @param segments the list of name segments, possibly empty + * @return the dot-separated qualified name, or the empty string + * + * @since 2.0 + */ +public static char[] toQualifiedName(char[][] segments) { + int length = segments.length; + if (length == 0) return CharOperation.NO_CHAR; + if (length == 1) return segments[0]; + + int resultLength = 0; + for (int i = 0; i < length; i++) { + resultLength += segments[i].length+1; + } + resultLength--; + char[] result = new char[resultLength]; + int index = 0; + for (int i = 0; i < length; i++) { + char[] segment = segments[i]; + int segmentLength = segment.length; + System.arraycopy(segment, 0, result, index, segmentLength); + index += segmentLength; + if (i != length-1) { + result[index++] = C_DOT; + } + } + return result; +} + +/** + * Converts the given array of qualified name segments to a qualified name. + *

+ * For example: + *

+ * 
+ * toQualifiedName(new String[] {"java", "lang", "Object"}) -> "java.lang.Object"
+ * toQualifiedName(new String[] {"Object"}) -> "Object"
+ * toQualifiedName(new String[0]) -> ""
+ * 
+ * 
+ *

+ * + * @param segments the list of name segments, possibly empty + * @return the dot-separated qualified name, or the empty string + */ +public static String toQualifiedName(String[] segments) { + int length = segments.length; + char[][] charArrays = new char[length][]; + for (int i = 0; i < length; i++) { + charArrays[i] = segments[i].toCharArray(); + } + return new String(toQualifiedName(charArrays)); +} +/** + * Converts the given type signature to a readable string. The signature is expected to + * be dot-based. + * + *

+ * For example: + *

+ * 
+ * toString("[Ljava.lang.String;") -> "java.lang.String[]"
+ * toString("I") -> "int"
+ * toString("+QObject;") -> "? extends Object"
+ * 
+ * 
+ *

+ *

+ * Note: This method assumes that a type signature containing a '$' + * is an inner type signature. While this is correct in most cases, someone could + * define a non-inner type name containing a '$'. Handling this + * correctly in all cases would have required resolving the signature, which + * generally not feasible. + *

+ * + * @param signature the type signature + * @return the string representation of the type + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static String toString(String signature) throws IllegalArgumentException { + return new String(toCharArray(signature.toCharArray())); +} +/** + * Converts the given method signature to a readable string. The method signature is expected to + * be dot-based. + * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @see #toCharArray(char[], char[], char[][], boolean, boolean) + * @return the string representation of the method signature + */ +public static String toString(String methodSignature, String methodName, String[] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType) { + return toString(methodSignature, methodName, parameterNames, fullyQualifyTypeNames, includeReturnType, false); +} +/** + * Converts the given method signature to a readable string. The method signature is expected to + * be dot-based. + * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @param isVarArgs true if the last argument should be displayed as a + * variable argument, false otherwise + * @see #toCharArray(char[], char[], char[][], boolean, boolean) + * @return the string representation of the method signature + * + * @since 3.1 + */ +public static String toString(String methodSignature, String methodName, String[] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType, boolean isVarArgs) { + char[][] params; + if (parameterNames == null) { + params = null; + } else { + int paramLength = parameterNames.length; + params = new char[paramLength][]; + for (int i = 0; i < paramLength; i++) { + params[i] = parameterNames[i].toCharArray(); + } + } + return new String(toCharArray(methodSignature.toCharArray(), methodName == null ? null : methodName.toCharArray(), params, fullyQualifyTypeNames, includeReturnType, isVarArgs)); +} +private Signature() { + // Not instantiable +} +} diff --git a/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java new file mode 100644 index 000000000..732695f62 --- /dev/null +++ b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2006, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.tool; + +import com.itsaky.androidide.services.compiler.CompilerProgress; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Set; +import java.util.WeakHashMap; +import javax.annotation.processing.Processor; +import javax.lang.model.SourceVersion; +import javax.tools.DiagnosticListener; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; +import org.eclipse.jdt.internal.compiler.batch.Main; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +/** + * Implementation of a batch compiler that supports the jsr199 + */ +public class EclipseCompiler implements JavaCompiler { + + private static Set SupportedSourceVersions; + + static { + // Eclipse compiler supports all possible versions from version 0 to + // version 6 + // we don't care about the order + EnumSet enumSet = EnumSet.range(SourceVersion.RELEASE_0, SourceVersion.RELEASE_6); + // we don't want anybody to modify this list + EclipseCompiler.SupportedSourceVersions = Collections.unmodifiableSet(enumSet); + } + + WeakHashMap threadCache; + public DiagnosticListener diagnosticListener; + + private CompilerProgress progress; + + public EclipseCompiler() { + this.threadCache = new WeakHashMap<>(); + } + + public EclipseCompiler setCompilationCancelled() { + return setCompilationCancelled(true); + } + + public EclipseCompiler setCompilationCancelled(boolean p0) { + if(this.progress != null) + this.progress.setCanceled(p0); + return this; + } + + public EclipseCompiler newProgress() { + this.progress = new CompilerProgress(); + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.tools.Tool#getSourceVersions() + */ + @Override + public Set getSourceVersions() { + return EclipseCompiler.SupportedSourceVersions; + } + /* + * (non-Javadoc) + * + * @see javax.tools.JavaCompiler#getStandardFileManager(javax.tools.DiagnosticListener, + * java.util.Locale, java.nio.charset.Charset) + */ + @Override + public StandardJavaFileManager getStandardFileManager(DiagnosticListener someDiagnosticListener, Locale locale, Charset charset) { + this.diagnosticListener = someDiagnosticListener; + return new EclipseFileManager(locale, charset); + } + /* + * (non-Javadoc) + * + * @see javax.tools.JavaCompiler#getTask(java.io.Writer, + * javax.tools.JavaFileManager, javax.tools.DiagnosticListener, + * java.lang.Iterable, java.lang.Iterable, java.lang.Iterable) + */ + @Override + public CompilationTask getTask(Writer out, JavaFileManager fileManager, DiagnosticListener someDiagnosticListener, Iterable options, Iterable classes, Iterable compilationUnits) { + PrintWriter writerOut = null; + PrintWriter writerErr = null; + if (out == null) { + writerOut = new PrintWriter(System.err); + writerErr = new PrintWriter(System.err); + } else { + writerOut = new PrintWriter(out); + writerErr = new PrintWriter(out); + } + final Thread currentThread = Thread.currentThread(); + EclipseCompilerImpl eclipseCompiler = this.threadCache.get(currentThread); + if (eclipseCompiler == null) { + eclipseCompiler = new EclipseCompilerImpl(writerOut, writerErr, false); + this.threadCache.put(currentThread, eclipseCompiler); + } else { + eclipseCompiler.initialize(writerOut, writerErr, false, null/*options*/, null/*progress*/); + } + final EclipseCompilerImpl eclipseCompiler2 = new EclipseCompilerImpl(writerOut, writerErr, false); + eclipseCompiler2.compilationUnits = compilationUnits; + eclipseCompiler2.diagnosticListener = someDiagnosticListener; + eclipseCompiler2.progress = eclipseCompiler.progress = this.progress; + if (fileManager != null) { + eclipseCompiler2.fileManager = fileManager; + } else { + eclipseCompiler2.fileManager = this.getStandardFileManager(someDiagnosticListener, null, null); + } + + eclipseCompiler2.options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_6); + eclipseCompiler2.options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_6); + eclipseCompiler2.options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_6); + + ArrayList allOptions = new ArrayList<>(); + if (options != null) { + for (Iterator iterator = options.iterator(); iterator.hasNext(); ) { + eclipseCompiler2.fileManager.handleOption(iterator.next(), iterator); + } + for (String option : options) { + allOptions.add(option); + } + } + + if (compilationUnits != null) { + for (JavaFileObject javaFileObject : compilationUnits) { + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6419926 + // compells us to check that the returned URIs are absolute, + // which they happen not to be for the default compiler on some + // unices + URI uri = javaFileObject.toUri(); + if (!uri.isAbsolute()) { + uri = URI.create("file://" + uri.toString()); //$NON-NLS-1$ + } + if (uri.getScheme().equals("file")) { //$NON-NLS-1$ + allOptions.add(new File(uri).getAbsolutePath()); + } else { + allOptions.add(uri.toString()); + } + } + } + + if (classes != null) { + allOptions.add("-classNames"); //$NON-NLS-1$ + StringBuilder builder = new StringBuilder(); + int i = 0; + for (String className : classes) { + if (i != 0) { + builder.append(','); + } + builder.append(className); + i++; + } + allOptions.add(String.valueOf(builder)); + } + + final String[] optionsToProcess = new String[allOptions.size()]; + allOptions.toArray(optionsToProcess); + try { + eclipseCompiler2.configure(optionsToProcess); + } catch (IllegalArgumentException e) { + throw e; + } + + if (eclipseCompiler2.fileManager instanceof StandardJavaFileManager) { + StandardJavaFileManager javaFileManager = (StandardJavaFileManager) eclipseCompiler2.fileManager; + Iterable location = javaFileManager.getLocation(StandardLocation.CLASS_OUTPUT); + if (location != null) { + eclipseCompiler2.setDestinationPath(location.iterator().next().getAbsolutePath()); + } + } + + return new CompilationTask() { + private boolean hasRun = false; + @Override + public Boolean call() { + // set up compiler with passed options + if (this.hasRun) { + throw new IllegalStateException("This task has already been run"); //$NON-NLS-1$ + } + Boolean value = eclipseCompiler2.call() ? Boolean.TRUE : Boolean.FALSE; + this.hasRun = true; + return value; + } + @Override + public void setLocale(Locale locale) { + eclipseCompiler2.setLocale(locale); + } + @Override + public void setProcessors(Iterable processors) { + ArrayList temp = new ArrayList<>(); + for (Processor processor : processors) { + temp.add(processor); + } + Processor[] processors2 = new Processor[temp.size()]; + temp.toArray(processors2); + eclipseCompiler2.processors = processors2; + } + }; + } + /* + * (non-Javadoc) + * + * @see javax.tools.OptionChecker#isSupportedOption(java.lang.String) + */ + @Override + public int isSupportedOption(String option) { + return Options.processOptions(option); + } + + /* + * (non-Javadoc) + * + * @see javax.tools.Tool#run(java.io.InputStream, java.io.OutputStream, + * java.io.OutputStream, java.lang.String[]) + */ + @Override + public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) { + boolean succeed = new Main( + new PrintWriter(new OutputStreamWriter(out != null ? out : System.out)), + new PrintWriter(new OutputStreamWriter(err != null ? err : System.err)), + true/* systemExit */, + null/* options */, + null/* progress */).compile(arguments); + return succeed ? 0 : -1; + } +} diff --git a/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java new file mode 100644 index 000000000..d0f7e40a7 --- /dev/null +++ b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java @@ -0,0 +1,594 @@ +/******************************************************************************* + * Copyright (c) 2007, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * IBM Corporation - fix for 342936 + * Kenneth Olson - Contribution for bug 188796 - [jsr199] Using JSR199 to extend ECJ + * Dennis Hendriks - Contribution for bug 188796 - [jsr199] Using JSR199 to extend ECJ + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.tool; + +import com.blankj.utilcode.util.ThrowableUtils; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Processor; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.compiler.CompilationProgress; +import org.eclipse.jdt.internal.compiler.ClassFile; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.batch.ClasspathJsr199; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; +import org.eclipse.jdt.internal.compiler.batch.FileSystem; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.Main; +import org.eclipse.jdt.internal.compiler.problem.AbortCompilationUnit; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; +import org.eclipse.jdt.internal.compiler.util.Messages; +import org.eclipse.jdt.internal.compiler.util.SuffixConstants; + +public class EclipseCompilerImpl extends Main { + private static final CompilationUnit[] NO_UNITS = new CompilationUnit[0]; + private HashMap javaFileObjectMap; + Iterable compilationUnits; + public JavaFileManager fileManager; + protected Processor[] processors; + public DiagnosticListener diagnosticListener; + + public EclipseCompilerImpl(PrintWriter out, PrintWriter err, boolean systemExitWhenFinished) { + super(out, err, systemExitWhenFinished, null/*options*/, null/*progress*/); + } + + public boolean call() { + try { + if(proceed) { + this.globalProblemsCount = 0; + this.globalErrorsCount = 0; + this.globalWarningsCount = 0; + this.globalTasksCount = 0; + this.exportedClassFilesCounter = 0; + // request compilation + performCompilation(); + } + } catch(IllegalArgumentException e) { + this.logger.logException(e); + com.itsaky.androidide.utils.Logger.instance().e("[EclipseCompilerImpl]", ThrowableUtils.getFullStackTrace(e)); + if (this.systemExitWhenFinished) { + cleanup(); + System.exit(-1); + } + return false; + } catch (RuntimeException e) { // internal compiler failure + com.itsaky.androidide.utils.Logger.instance().e("[EclipseCompilerImpl]", ThrowableUtils.getFullStackTrace(e)); + this.logger.logException(e); + return false; + } finally { + cleanup(); + } + if (this.globalErrorsCount == 0) + return true; + return false; + } + + private void cleanup() { + this.logger.flush(); + this.logger.close(); + this.processors = null; + try { + if (this.fileManager != null) { + this.fileManager.flush(); + } + } catch (IOException e) { + // ignore + } + } + + @Override + public CompilationUnit[] getCompilationUnits() { + if (this.compilationUnits == null) return EclipseCompilerImpl.NO_UNITS; + ArrayList units = new ArrayList<>(); + for (final JavaFileObject javaFileObject : this.compilationUnits) { + if (javaFileObject.getKind() != JavaFileObject.Kind.SOURCE) { + throw new IllegalArgumentException(); + } + String name = javaFileObject.getName(); + name = name.replace('\\', '/'); + CompilationUnit compilationUnit = new CompilationUnit(null, + name, + null) { + + @Override + public char[] getContents() { + try { + return javaFileObject.getCharContent(true).toString().toCharArray(); + } catch(IOException e) { + e.printStackTrace(); + throw new AbortCompilationUnit(null, e, null); + } + } + }; + units.add(compilationUnit); + this.javaFileObjectMap.put(compilationUnit, javaFileObject); + } + CompilationUnit[] result = new CompilationUnit[units.size()]; + units.toArray(result); + return result; + } + /* + * Low-level API performing the actual compilation + */ + @Override + public IErrorHandlingPolicy getHandlingPolicy() { + // passes the initial set of files to the batch oracle (to avoid finding more than once the same units when case insensitive match) + return new IErrorHandlingPolicy() { + @Override + public boolean proceedOnErrors() { + return false; // stop if there are some errors + } + @Override + public boolean stopOnFirstError() { + return false; + } + @Override + public boolean ignoreAllErrors() { + return false; + } + }; + } + + @Override + public IProblemFactory getProblemFactory() { + return new DefaultProblemFactory() { + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + DiagnosticListener diagListener = EclipseCompilerImpl.this.diagnosticListener; + if (diagListener != null) { + diagListener.report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + } + return super.createProblem(originatingFileName, problemId, problemArguments, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final int elaborationID, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + DiagnosticListener diagListener = EclipseCompilerImpl.this.diagnosticListener; + if (diagListener != null) { + diagListener.report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Info) != 0) { + return Diagnostic.Kind.NOTE; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + } + return super.createProblem(originatingFileName, problemId, problemArguments, elaborationID, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + }; + } + + @Override + protected void initialize(PrintWriter outWriter, PrintWriter errWriter, boolean systemExit, Map customDefaultOptions, CompilationProgress compilationProgress) { + super.initialize(outWriter, errWriter, systemExit, customDefaultOptions, compilationProgress); + this.javaFileObjectMap = new HashMap<>(); + } + + @Override + protected void initializeAnnotationProcessorManager() { + super.initializeAnnotationProcessorManager(); + if (this.batchCompiler.annotationProcessorManager != null && this.processors != null) { + this.batchCompiler.annotationProcessorManager.setProcessors(this.processors); + } else if (this.processors != null) { + throw new UnsupportedOperationException("Cannot handle annotation processing"); //$NON-NLS-1$ + } + } + + // Dump classfiles onto disk for all compilation units that where successful + // and do not carry a -d none spec, either directly or inherited from Main. + @Override + public void outputClassFiles(CompilationResult unitResult) { + if (!((unitResult == null) || (unitResult.hasErrors() && !this.proceedOnError))) { + ClassFile[] classFiles = unitResult.getClassFiles(); + boolean generateClasspathStructure = this.fileManager.hasLocation(StandardLocation.CLASS_OUTPUT); + String currentDestinationPath = this.destinationPath; + File outputLocation = null; + if (currentDestinationPath != null) { + outputLocation = new File(currentDestinationPath); + outputLocation.mkdirs(); + } + for (int i = 0, fileCount = classFiles.length; i < fileCount; i++) { + // retrieve the key and the corresponding classfile + ClassFile classFile = classFiles[i]; + char[] filename = classFile.fileName(); + int length = filename.length; + char[] relativeName = new char[length + 6]; + System.arraycopy(filename, 0, relativeName, 0, length); + System.arraycopy(SuffixConstants.SUFFIX_class, 0, relativeName, length, 6); + CharOperation.replace(relativeName, '/', File.separatorChar); + String relativeStringName = new String(relativeName); + if (this.compilerOptions.verbose) { + EclipseCompilerImpl.this.out.println( + Messages.bind( + Messages.compilation_write, + new String[] { + String.valueOf(this.exportedClassFilesCounter+1), + relativeStringName + })); + } + try { + JavaFileObject javaFileForOutput = + this.fileManager.getJavaFileForOutput( + StandardLocation.CLASS_OUTPUT, + new String(filename), + JavaFileObject.Kind.CLASS, + this.javaFileObjectMap.get(unitResult.compilationUnit)); + + if (generateClasspathStructure) { + if (currentDestinationPath != null) { + int index = CharOperation.lastIndexOf(File.separatorChar, relativeName); + if (index != -1) { + File currentFolder = new File(currentDestinationPath, relativeStringName.substring(0, index)); + currentFolder.mkdirs(); + } + } else { + // create the subfolfers is necessary + // need a way to retrieve the folders to create + String path = javaFileForOutput.toUri().getPath(); + int index = path.lastIndexOf('/'); + if (index != -1) { + File file = new File(path.substring(0, index)); + file.mkdirs(); + } + } + } + + try (OutputStream openOutputStream = javaFileForOutput.openOutputStream(); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(openOutputStream)) { + bufferedOutputStream.write(classFile.header, 0, classFile.headerOffset); + bufferedOutputStream.write(classFile.contents, 0, classFile.contentsOffset); + bufferedOutputStream.flush(); + } + } catch (IOException e) { + this.logger.logNoClassFileCreated(currentDestinationPath, relativeStringName, e); + } + this.logger.logClassFile( + generateClasspathStructure, + currentDestinationPath, + relativeStringName); + this.exportedClassFilesCounter++; + } + this.batchCompiler.lookupEnvironment.releaseClassFiles(classFiles); + } + } + + @Override + protected void setPaths(ArrayList bootclasspaths, + String sourcepathClasspathArg, + ArrayList sourcepathClasspaths, + ArrayList classpaths, + ArrayList extdirsClasspaths, + ArrayList endorsedDirClasspaths, + String customEncoding) { + + ArrayList fileSystemClasspaths = new ArrayList<>(); + EclipseFileManager eclipseJavaFileManager = null; + StandardJavaFileManager standardJavaFileManager = null; + JavaFileManager javaFileManager = null; + boolean havePlatformPaths = false; + boolean haveClassPaths = false; + if (this.fileManager instanceof EclipseFileManager) { + eclipseJavaFileManager = (EclipseFileManager) this.fileManager; + } + if (this.fileManager instanceof StandardJavaFileManager) { + standardJavaFileManager = (StandardJavaFileManager) this.fileManager; + } + javaFileManager = this.fileManager; + + if (eclipseJavaFileManager != null) { + if ((eclipseJavaFileManager.flags & EclipseFileManager.HAS_ENDORSED_DIRS) == 0 + && (eclipseJavaFileManager.flags & EclipseFileManager.HAS_BOOTCLASSPATH) != 0) { + fileSystemClasspaths.addAll(this.handleEndorseddirs(null)); + } + } + Iterable location = null; + if (standardJavaFileManager != null) { + location = standardJavaFileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH); + if (location != null) { + for (File file : location) { + Classpath classpath = FileSystem.getClasspath( + file.getAbsolutePath(), + null, + null, this.options); + if (classpath != null) { + fileSystemClasspaths.add(classpath); + havePlatformPaths = true; + } + } + } + } else if (javaFileManager != null) { + Classpath classpath = new ClasspathJsr199(this.fileManager, StandardLocation.PLATFORM_CLASS_PATH); + fileSystemClasspaths.add(classpath); + havePlatformPaths = true; + } + if (eclipseJavaFileManager != null) { + if ((eclipseJavaFileManager.flags & EclipseFileManager.HAS_EXT_DIRS) == 0 + && (eclipseJavaFileManager.flags & EclipseFileManager.HAS_BOOTCLASSPATH) != 0) { + fileSystemClasspaths.addAll(this.handleExtdirs(null)); + } + } + if (standardJavaFileManager != null) { + location = standardJavaFileManager.getLocation(StandardLocation.SOURCE_PATH); + if (location != null) { + for (File file : location) { + Classpath classpath = FileSystem.getClasspath( + file.getAbsolutePath(), + null, + null, this.options); + if (classpath != null) { + fileSystemClasspaths.add(classpath); + } + } + } + location = standardJavaFileManager.getLocation(StandardLocation.CLASS_PATH); + if (location != null) { + for (File file : location) { + Classpath classpath = FileSystem.getClasspath( + file.getAbsolutePath(), + null, + null, this.options); + if (classpath != null) { + fileSystemClasspaths.add(classpath); + haveClassPaths = true; + } + } + } + } else if (javaFileManager != null) { + Classpath classpath = null; + if (this.fileManager.hasLocation(StandardLocation.SOURCE_PATH)) { + classpath = new ClasspathJsr199(this.fileManager, StandardLocation.SOURCE_PATH); + fileSystemClasspaths.add(classpath); + } + classpath = new ClasspathJsr199(this.fileManager, StandardLocation.CLASS_PATH); + fileSystemClasspaths.add(classpath); + haveClassPaths = true; + } + if (this.checkedClasspaths == null) { + // It appears to be necessary to handleBootclasspath() for IBM JVMs + // in order to have visibility to java.lang.String (not present in rt.jar). + // The jars returned by StandardFileManager.getLocation(PLATFORM_CLASS_PATH) are + // not sufficient to resolve all standard classes. + if (!havePlatformPaths) fileSystemClasspaths.addAll(this.handleBootclasspath(null, null)); + if (!haveClassPaths) fileSystemClasspaths.addAll(this.handleClasspath(null, null)); + } + fileSystemClasspaths = FileSystem.ClasspathNormalizer.normalize(fileSystemClasspaths); + final int size = fileSystemClasspaths.size(); + if (size != 0) { + this.checkedClasspaths = new FileSystem.Classpath[size]; + int i = 0; + for (FileSystem.Classpath classpath : fileSystemClasspaths) { + this.checkedClasspaths[i++] = classpath; + } + } + } + @Override + protected void loggingExtraProblems() { + super.loggingExtraProblems(); + for (@SuppressWarnings("rawtypes") + Iterator iterator = this.extraProblems.iterator(); iterator.hasNext(); ) { + final CategorizedProblem problem = (CategorizedProblem) iterator.next(); + if (this.diagnosticListener != null) { + this.diagnosticListener.report(new Diagnostic() { + @Override + public String getCode() { + return null; + } + @Override + public long getColumnNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).column; + } + return Diagnostic.NOPOS; + } + @Override + public long getEndPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceEnd(); + } + return Diagnostic.NOPOS; + } + @Override + public Kind getKind() { + if (problem.isError()) { + return Diagnostic.Kind.ERROR; + } + if (problem.isWarning()) { + return Diagnostic.Kind.WARNING; + } else if (problem instanceof DefaultProblem && ((DefaultProblem) problem).isInfo()) { + return Diagnostic.Kind.NOTE; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceLineNumber(); + } + return Diagnostic.NOPOS; + } + @Override + public String getMessage(Locale locale) { + return problem.getMessage(); + } + @Override + public long getPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceStart(); + } + return Diagnostic.NOPOS; + } + @Override + public JavaFileObject getSource() { + if (problem instanceof DefaultProblem) { + File f = new File(new String(((DefaultProblem) problem).getOriginatingFileName())); + if (f.exists()) { + Charset charset = (EclipseCompilerImpl.this.fileManager instanceof EclipseFileManager) ? + ((EclipseFileManager) EclipseCompilerImpl.this.fileManager).charset : Charset.defaultCharset(); + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, charset); + } + return null; + } + return null; + } + @Override + public long getStartPosition() { + return getPosition(); + } + }); + } + } + } +} diff --git a/editor/src/main/res/anim/popup_in.xml b/app/src/main/res/anim/popup_in.xml similarity index 100% rename from editor/src/main/res/anim/popup_in.xml rename to app/src/main/res/anim/popup_in.xml diff --git a/editor/src/main/res/anim/popup_out.xml b/app/src/main/res/anim/popup_out.xml similarity index 100% rename from editor/src/main/res/anim/popup_out.xml rename to app/src/main/res/anim/popup_out.xml diff --git a/app/src/main/res/drawable/ic_compilation_error.xml b/app/src/main/res/drawable/ic_compilation_error.xml new file mode 100644 index 000000000..e0768321f --- /dev/null +++ b/app/src/main/res/drawable/ic_compilation_error.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_diagnostics.xml b/app/src/main/res/drawable/ic_diagnostics.xml new file mode 100644 index 000000000..c9b814565 --- /dev/null +++ b/app/src/main/res/drawable/ic_diagnostics.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml index 66773f0fb..7156b37b7 100644 --- a/app/src/main/res/drawable/ic_info.xml +++ b/app/src/main/res/drawable/ic_info.xml @@ -5,7 +5,7 @@ android:viewportHeight="24" android:viewportWidth="24"> - + \ No newline at end of file diff --git a/editor/src/main/res/drawable/ic_more_vert_black_20dp.xml b/app/src/main/res/drawable/ic_more_vert_black_20dp.xml similarity index 100% rename from editor/src/main/res/drawable/ic_more_vert_black_20dp.xml rename to app/src/main/res/drawable/ic_more_vert_black_20dp.xml diff --git a/editor/src/main/res/drawable/round_content_copy_20.xml b/app/src/main/res/drawable/round_content_copy_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_content_copy_20.xml rename to app/src/main/res/drawable/round_content_copy_20.xml diff --git a/editor/src/main/res/drawable/round_content_cut_20.xml b/app/src/main/res/drawable/round_content_cut_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_content_cut_20.xml rename to app/src/main/res/drawable/round_content_cut_20.xml diff --git a/editor/src/main/res/drawable/round_content_paste_20.xml b/app/src/main/res/drawable/round_content_paste_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_content_paste_20.xml rename to app/src/main/res/drawable/round_content_paste_20.xml diff --git a/editor/src/main/res/drawable/round_save_20.xml b/app/src/main/res/drawable/round_save_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_save_20.xml rename to app/src/main/res/drawable/round_save_20.xml diff --git a/editor/src/main/res/drawable/round_select_all_20.xml b/app/src/main/res/drawable/round_select_all_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_select_all_20.xml rename to app/src/main/res/drawable/round_select_all_20.xml diff --git a/app/src/main/res/layout/activity_editor.xml b/app/src/main/res/layout/activity_editor.xml index b6198bfaf..857e904f3 100644 --- a/app/src/main/res/layout/activity_editor.xml +++ b/app/src/main/res/layout/activity_editor.xml @@ -133,6 +133,23 @@ app:icon="@drawable/ic_build_output" app:backgroundTint="@color/primaryColor" app:rippleColor="@color/rippleHighlightColor_light"/> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_diagnostic_info.xml b/app/src/main/res/layout/layout_diagnostic_info.xml new file mode 100644 index 000000000..af339099c --- /dev/null +++ b/app/src/main/res/layout/layout_diagnostic_info.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_diagnostic_item.xml b/app/src/main/res/layout/layout_diagnostic_item.xml new file mode 100644 index 000000000..b0c02d250 --- /dev/null +++ b/app/src/main/res/layout/layout_diagnostic_item.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/editor/src/main/res/layout/problem_view.xml b/app/src/main/res/layout/problem_view.xml similarity index 100% rename from editor/src/main/res/layout/problem_view.xml rename to app/src/main/res/layout/problem_view.xml diff --git a/editor/src/main/res/layout/text_compose_panel.xml b/app/src/main/res/layout/text_compose_panel.xml similarity index 100% rename from editor/src/main/res/layout/text_compose_panel.xml rename to app/src/main/res/layout/text_compose_panel.xml diff --git a/editor/src/main/res/layout/text_compose_popup_window.xml b/app/src/main/res/layout/text_compose_popup_window.xml similarity index 100% rename from editor/src/main/res/layout/text_compose_popup_window.xml rename to app/src/main/res/layout/text_compose_popup_window.xml diff --git a/app/src/main/res/menu/menu_editor_nav.xml b/app/src/main/res/menu/menu_editor_nav.xml index 029787291..4fd89364c 100644 --- a/app/src/main/res/menu/menu_editor_nav.xml +++ b/app/src/main/res/menu/menu_editor_nav.xml @@ -9,10 +9,10 @@ android:id="@+id/editornav_discuss" android:icon="@drawable/ic_telegram" android:title="@string/discussions_on_telegram"/> - + android:title="@string/user_suggestions"/> #bdbdbd #70ffffff #50ffffff + #f0ffffff #90424242 #ffffff #9f9f9f @@ -32,7 +33,13 @@ #FF7043 #4CAF50 @color/primaryTextColor + @color/secondaryDarkColor + @color/log_warning #212121 #4CAF50 #70000000 + + @android:color/tertiary_text_light + #eeeeee + @android:color/black diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a853a468..4e5188394 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,9 @@ + Diagnostics + Line: %s, Column: %s + ERROR + WARNING + NOTE Project deleted Failed to delete project! Failed to load icon path! @@ -224,4 +229,18 @@ Notifications from Developers App Update Notifications Nothing here … + + Paste + Copy + Select All + Project + Cut + Last + Next + Replace + Replace All + Replacement + Text to search + More + Long click on an item to show JavaDoc if available. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index bf47b13af..af392f016 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -103,4 +103,71 @@ @color/secondaryLightColor @color/secondaryColor + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index c2aa5bdf6..8a82caabf 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,8 @@ buildscript { buildTools = "30.0.2" minSdk = 21 targetSdk = 28 - versionCode = 101210625 - versionName = "1.0.1-alpha" + versionCode = 102210625 + versionName = "1.0.2-alpha" packageName = "com.itsaky.androidide" } diff --git a/editor/.gitignore b/editor/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/editor/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/editor/build.gradle b/editor/build.gradle deleted file mode 100644 index 85b258431..000000000 --- a/editor/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ - -apply plugin: 'com.android.library' - -android { - compileSdkVersion rootProject.ext.compileSdk - buildToolsVersion rootProject.ext.buildTools - - defaultConfig { - minSdkVersion rootProject.ext.minSdk - targetSdkVersion rootProject.ext.targetSdk - versionCode rootProject.ext.versionCode - versionName rootProject.ext.versionName - vectorDrawables.useSupportLibrary = true - } - - buildFeatures { - viewBinding true - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation 'com.blankj:utilcodex:1.30.6' - implementation 'com.elvishew:xlog:1.8.0' - implementation 'com.github.Rosemoe:BlockLinkedList:1.0.0' - - implementation 'androidx.annotation:annotation:1.2.0' - implementation 'androidx.vectordrawable:vectordrawable:1.1.0' - implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' - implementation 'com.google.android.material:material:1.3.0' - implementation fileTree(dir: "libs", include: ["*.jar"]) -} diff --git a/editor/consumer-rules.pro b/editor/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/editor/proguard-rules.pro b/editor/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/editor/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/editor/src/main/AndroidManifest.xml b/editor/src/main/AndroidManifest.xml deleted file mode 100644 index ef8d25f33..000000000 --- a/editor/src/main/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/editor/src/main/res/drawable/bg_ripple.xml b/editor/src/main/res/drawable/bg_ripple.xml deleted file mode 100644 index 9b1a5df11..000000000 --- a/editor/src/main/res/drawable/bg_ripple.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/editor/src/main/res/drawable/bg_ripple_disabled.xml b/editor/src/main/res/drawable/bg_ripple_disabled.xml deleted file mode 100644 index 34782662b..000000000 --- a/editor/src/main/res/drawable/bg_ripple_disabled.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/editor/src/main/res/drawable/bg_ripple_pressed.xml b/editor/src/main/res/drawable/bg_ripple_pressed.xml deleted file mode 100644 index b8c1e9872..000000000 --- a/editor/src/main/res/drawable/bg_ripple_pressed.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/editor/src/main/res/drawable/ic_info.xml b/editor/src/main/res/drawable/ic_info.xml deleted file mode 100644 index 7156b37b7..000000000 --- a/editor/src/main/res/drawable/ic_info.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/editor/src/main/res/values-zh/strings.xml b/editor/src/main/res/values-zh/strings.xml deleted file mode 100644 index 31991a6f0..000000000 --- a/editor/src/main/res/values-zh/strings.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - 粘贴 - 复制 - 全选 - 剪切 - 上个 - 下个 - 替换 - 替换全部 - 替换文本 - 取消 - 欲搜索文本 - \ No newline at end of file diff --git a/editor/src/main/res/values/attrs.xml b/editor/src/main/res/values/attrs.xml deleted file mode 100644 index c8c1cb818..000000000 --- a/editor/src/main/res/values/attrs.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/editor/src/main/res/values/colors.xml b/editor/src/main/res/values/colors.xml deleted file mode 100644 index 00486d6b4..000000000 --- a/editor/src/main/res/values/colors.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - @android:color/tertiary_text_light - #90010101 - #eeeeee - @android:color/black - diff --git a/editor/src/main/res/values/dimens.xml b/editor/src/main/res/values/dimens.xml deleted file mode 100644 index 55344e519..000000000 --- a/editor/src/main/res/values/dimens.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/editor/src/main/res/values/strings.xml b/editor/src/main/res/values/strings.xml deleted file mode 100644 index 68c370951..000000000 --- a/editor/src/main/res/values/strings.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - Paste - Copy - Select All - Project - Cut - Last - Next - Replace - Replace All - Replacement - Cancel - Text to search - More - Long click on an item to show JavaDoc if available. - diff --git a/editor/src/main/res/values/styles.xml b/editor/src/main/res/values/styles.xml deleted file mode 100644 index 6c67ea2cf..000000000 --- a/editor/src/main/res/values/styles.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle b/settings.gradle index c6e33a765..cee3ae1c0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,5 @@ include ':app' include ':treeview' -include ':editor' include ':spinkit' include ':slidr' include ':javadocparser'