| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * 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. |
| */ |
| package com.google.appindexing.api; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.VisibleForTesting; |
| import com.android.builder.model.AndroidArtifact; |
| import com.android.builder.model.AndroidLibrary; |
| import com.android.builder.model.Dependencies; |
| import com.android.builder.model.MavenCoordinates; |
| import com.android.ide.common.repository.GradleCoordinate; |
| import com.android.tools.idea.gradle.project.model.AndroidModuleModel; |
| import com.android.tools.idea.gradle.dsl.model.GradleBuildModel; |
| import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel; |
| import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencySpec; |
| import com.android.tools.idea.gradle.dsl.model.dependencies.DependenciesModel; |
| import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker; |
| import com.google.appindexing.util.DeepLinkUtils; |
| import com.google.appindexing.util.ManifestUtils; |
| import com.google.common.base.CharMatcher; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.intellij.ide.highlighter.JavaFileType; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.lang.xml.XMLLanguage; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.xml.XmlComment; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.siyeh.ig.psiutils.ImportUtils; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import static com.android.tools.idea.gradle.dsl.model.dependencies.CommonConfigurationNames.COMPILE; |
| import static com.android.tools.idea.gradle.util.GradleUtil.getDependencies; |
| |
| /** |
| * A class for detecting the situation where developers need App Indexing API code and inserting |
| * the API code in their projects. |
| */ |
| public final class ApiCreator { |
| static private final String COMPILE_GMS_GROUP = "com.google.android.gms"; |
| static private final String COMPILE_APPINDEXING = "play-services-appindexing"; |
| static private final String MINIMUM_VERSION = "8.4.0"; |
| |
| static private final String TAG_METADATA = "meta-data"; |
| static private final String ATTR_NAME_METADATA_GMS = "com.google.android.gms.version"; |
| static private final String ATTR_VALUE_METADATA_GMS = "@integer/google_play_services_version"; |
| |
| static private final String METHOD_GET_ACTION = "getIndexApiAction"; |
| |
| static private final String CLASS_ACTION = "Action"; |
| static private final String CLASS_APP_INDEX = "AppIndex"; |
| static private final String CLASS_GOOGLE_API_CLIENT = "GoogleApiClient"; |
| static private final String CLASS_THING = "Thing"; |
| static private final String CLASS_ACTION_FULL = "com.google.android.gms.appindexing.Action"; |
| static private final String CLASS_APP_INDEX_FULL = "com.google.android.gms.appindexing.AppIndex"; |
| static private final String CLASS_THING_FULL = "com.google.android.gms.appindexing.Thing"; |
| static private final String CLASS_GOOGLE_API_CLIENT_FULL = "com.google.android.gms.common.api.GoogleApiClient"; |
| |
| static private final List<String> SIGNATURE_ON_CREATE = Lists.newArrayList("android.os.Bundle"); |
| static private final List<String> SIGNATURE_ON_START = Lists.newArrayList(); |
| static private final List<String> SIGNATURE_ON_STOP = Lists.newArrayList(); |
| static private final String ON_CREATE_FORMAT = "@Override\n" + |
| "protected void onCreate(android.os.Bundle savedInstanceState) {\n" + |
| " super.onCreate(savedInstanceState);\n" + |
| " \n" + |
| " // ATTENTION: This was auto-generated to implement the App Indexing API.\n" + |
| " // See https://g.co/AppIndexing/AndroidStudio for more information.\n" + |
| " %1$s = new %2$s.Builder(this).addApi(%3$s.API).build();\n" + |
| "}"; |
| static private final String ON_START_FORMAT = "@Override\n" + |
| "public void onStart(){\n" + |
| " super.onStart();\n" + |
| " \n" + |
| " // ATTENTION: This was auto-generated to implement the App Indexing API.\n" + |
| " // See https://g.co/AppIndexing/AndroidStudio for more information.\n" + |
| " %1$s.connect();\n" + |
| " %3$s.AppIndexApi.start(%1$s, %2$s);\n" + |
| "}"; |
| static private final String ON_STOP_FORMAT = "@Override\n" + |
| "public void onStop() {\n" + |
| " super.onStop();\n" + |
| " \n" + |
| " // ATTENTION: This was auto-generated to implement the App Indexing API.\n" + |
| " // See https://g.co/AppIndexing/AndroidStudio for more information.\n" + |
| " %3$s.AppIndexApi.end(%1$s, %2$s);\n" + |
| " %1$s.disconnect();\n" + |
| "}"; |
| |
| static private final String GET_ACTION_FORMAT = "/**\n" + |
| " * ATTENTION: This was auto-generated to implement the App Indexing API.\n" + |
| " * See https://g.co/AppIndexing/AndroidStudio for more information.\n" + |
| " */\n" + |
| "public %4$s %1$s() {\n" + |
| " %5$s object = new %5$s.Builder()\n" + |
| " .setName(\"%2$s Page\") // TODO: Define a title for the content shown.\n" + |
| " // TODO: Make sure this auto-generated URL is correct.\n" + |
| " .setUrl(android.net.Uri.parse(\"%3$s\"))\n" + |
| " .build();\n" + |
| " return new %4$s.Builder(%4$s.TYPE_VIEW)\n" + |
| " .setObject(object)\n" + |
| " .setActionStatus(%4$s.STATUS_TYPE_COMPLETED)\n" + |
| " .build();\n" + |
| "}"; |
| |
| static private final String CLIENT_FORMAT = "%1$s = new %2$s.Builder(this).addApi(%3$s.API).build();"; |
| static private final String BUILDER_APPINDEXAPI = ".addApi(%s.API)"; |
| static private final String APP_INDEXING_START = "%3$s.AppIndexApi.start(%1$s,%2$s);"; |
| static private final String APP_INDEXING_END = "%3$s.AppIndexApi.end(%1$s,%2$s);"; |
| static private final String APP_INDEXING_START_HALF = "AppIndexApi.start(%1$s,"; |
| static private final String APP_INDEXING_END_HALF = "AppIndexApi.end(%1$s,"; |
| static private final String APP_INDEXING_VIEW = "AppIndexApi.view(%1$s,"; |
| static private final String APP_INDEXING_VIEWEND = "AppIndexApi.viewEnd(%1$s,"; |
| |
| static private final List<String> COMMENT_BUILDER_APPINDEXAPI = Lists |
| .newArrayList("// ATTENTION: This \"addApi(AppIndex.API)\"was auto-generated to implement the App Indexing API.", |
| "// See https://g.co/AppIndexing/AndroidStudio for more information."); |
| static private final List<String> COMMENT_IN_JAVA = Lists |
| .newArrayList("// ATTENTION: This was auto-generated to implement the App Indexing API.", |
| "// See https://g.co/AppIndexing/AndroidStudio for more information."); |
| static private final String COMMENT_FOR_FIELD = "/**\n" + |
| " * ATTENTION: This %1$swas auto-generated to implement the App Indexing API.\n" + |
| " * See https://g.co/AppIndexing/AndroidStudio for more information.\n" + |
| " */"; |
| static private final String COMMENT_IN_MANIFEST = |
| "<!-- ATTENTION: This was auto-generated to add Google Play services to your project for\n" + |
| " App Indexing. See https://g.co/AppIndexing/AndroidStudio for more information. -->"; |
| |
| /* To prompt developers to update deep link url, sets default url to a noticeable wrong url */ |
| static private final String DEFAULT_DEEP_LINK_URL = "http://[ENTER-YOUR-URL-HERE]"; |
| |
| |
| private Project myProject = null; |
| private Module myModule = null; |
| private PsiFile myFile = null; |
| |
| private PsiClass myActivity = null; |
| |
| private PsiElementFactory myFactory = null; |
| private CodeStyleManager myCodeStyleManager = null; |
| |
| private PsiCodeBlock myOnCreate = null; |
| private PsiCodeBlock myOnStart = null; |
| private PsiCodeBlock myOnStop = null; |
| |
| /* The App Indexing API statements in onStart / onStop method */ |
| private List<PsiStatement> myStartStatements = Lists.newArrayList(); |
| private List<PsiStatement> myEndStatements = Lists.newArrayList(); |
| private List<PsiStatement> myViewStatements = Lists.newArrayList(); |
| private List<PsiStatement> myViewEndStatements = Lists.newArrayList(); |
| |
| private String myHighestGmsLibVersion = null; |
| private String myAppIndexingLibVersion = null; |
| |
| private Map<String, String> myImportClasses = Maps.newHashMap(); |
| |
| public ApiCreator(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { |
| myProject = project; |
| myFile = file; |
| myModule = ModuleUtilCore.findModuleForFile(myFile.getVirtualFile(), myProject); |
| |
| if (myFile instanceof PsiJavaFile) { |
| PsiElement element = myFile.findElementAt(editor.getCaretModel().getOffset()); |
| if (element != null) { |
| myActivity = getSurroundingInheritingClass(element, SdkConstants.CLASS_ACTIVITY); |
| if (myActivity != null) { |
| myOnCreate = getMethodBodyByName("onCreate", SIGNATURE_ON_CREATE, myActivity); |
| myOnStart = getMethodBodyByName("onStart", SIGNATURE_ON_START, myActivity); |
| myOnStop = getMethodBodyByName("onStop", SIGNATURE_ON_STOP, myActivity); |
| if (myOnStart != null) { |
| myStartStatements = StatementFilter.filterCodeBlock("AppIndex.AppIndexApi.start", myOnStart); |
| myViewStatements = StatementFilter.filterCodeBlock("AppIndex.AppIndexApi.view", myOnStart); |
| } |
| if (myOnStop != null) { |
| myEndStatements = StatementFilter.filterCodeBlock("AppIndex.AppIndexApi.end", myOnStop); |
| myViewEndStatements = StatementFilter.filterCodeBlock("AppIndex.AppIndexApi.viewEnd", myOnStop); |
| } |
| } |
| } |
| } |
| myFactory = (PsiElementFactory)JVMElementFactories.getFactory(JavaLanguage.INSTANCE, myProject); |
| myCodeStyleManager = CodeStyleManager.getInstance(myProject); |
| myImportClasses.put(CLASS_ACTION, CLASS_ACTION_FULL); |
| myImportClasses.put(CLASS_APP_INDEX, CLASS_APP_INDEX_FULL); |
| myImportClasses.put(CLASS_GOOGLE_API_CLIENT, CLASS_GOOGLE_API_CLIENT_FULL); |
| myImportClasses.put(CLASS_THING, CLASS_THING_FULL); |
| } |
| |
| @VisibleForTesting |
| void setGmsLibVersion(@Nullable String version) { |
| myHighestGmsLibVersion = version; |
| myAppIndexingLibVersion = version; |
| } |
| |
| /** |
| * If the current caret's position is eligible for creating App Indexing API code, |
| * i.e. the caret is inside an 'Activity' class |
| * and the class does not call app indexing api. |
| * |
| * @return true if the current caret's position needs app indexing generating intention. |
| */ |
| public static boolean eligibleForInsertingAppIndexingApiCode(@NotNull Editor editor, @NotNull PsiFile file) { |
| if (!(file instanceof PsiJavaFile)) return false; |
| PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); |
| if (element == null) return false; |
| PsiClass activity = getSurroundingInheritingClass(element, SdkConstants.CLASS_ACTIVITY); |
| if (activity == null) return false; |
| PsiCodeBlock onStart = getMethodBodyByName("onStart", SIGNATURE_ON_START, activity); |
| if (onStart == null) return true; |
| PsiCodeBlock onStop = getMethodBodyByName("onStop", SIGNATURE_ON_STOP, activity); |
| if (onStop == null) return true; |
| List<PsiStatement> startStatements = StatementFilter.filterCodeBlock("AppIndexApi.start", onStart); |
| List<PsiStatement> viewStatements = StatementFilter.filterCodeBlock("AppIndexApi.view", onStart); |
| if (startStatements.isEmpty() && viewStatements.isEmpty()) return true; |
| List<PsiStatement> endStatements = StatementFilter.filterCodeBlock("AppIndexApi.end", onStop); |
| List<PsiStatement> viewEndStatements = StatementFilter.filterCodeBlock("AppIndexApi.viewEnd", onStop); |
| if (endStatements.isEmpty() && viewEndStatements.isEmpty()) return true; |
| return false; |
| } |
| |
| /** |
| * Inserts the AppIndexing API code for the activity where the caret is in. |
| * Steps: |
| * 1. Generates Google Play Service Support in build.gradle and AndroidManifest.xml of current |
| * module. |
| * 2. Generates App Indexing API code in Java source file. |
| * 3. Sync the project if build.gradle of module is changed. |
| */ |
| public void insertAppIndexingApiCodeForActivity() { |
| boolean needGradleSync = false; |
| try { |
| if (myModule == null || myActivity == null || myFactory == null || myCodeStyleManager == null) { |
| Logger.getInstance(ApiCreator.class).info("Unable to generate App Indexing API code."); |
| return; |
| } |
| |
| getGmsDependencyVersion(); |
| needGradleSync = insertGmsCompileDependencyInGradleIfNeeded(); |
| |
| XmlFile manifestPsiFile = ManifestUtils.getAndroidManifestPsi(myModule); |
| if (manifestPsiFile == null) { |
| Logger.getInstance(ApiCreator.class).info("AndroidManifest.xml not found."); |
| return; |
| } |
| insertGmsVersionTagInManifestIfNeeded(manifestPsiFile); |
| |
| insertAppIndexingApiCodeInJavaFile(getDeepLinkOfActivity(), !needGradleSync); |
| } |
| catch (ApiCreatorException e) { |
| e.printStackTrace(); |
| } |
| finally { |
| if (needGradleSync) { |
| GradleSyncInvoker.getInstance().requestProjectSyncAndSourceGeneration(myProject, null); |
| } |
| } |
| } |
| |
| private void getGmsDependencyVersion() throws ApiCreatorException { |
| AndroidModuleModel model = AndroidModuleModel.get(myModule); |
| if (model == null) { |
| throw new ApiCreatorException("AndroidGradleModel not found."); |
| } |
| AndroidArtifact artifact = model.getMainArtifact(); |
| Dependencies dependencies = getDependencies(artifact, model.getModelVersion()); |
| Collection<AndroidLibrary> libraries = dependencies.getLibraries(); |
| for (AndroidLibrary library : libraries) { |
| getDependencyVersionFromAndroidLibrary(library); |
| } |
| } |
| |
| private void getDependencyVersionFromAndroidLibrary(AndroidLibrary library) { |
| MavenCoordinates coordinates = library.getResolvedCoordinates(); |
| if (coordinates != null && coordinates.getGroupId().equals(COMPILE_GMS_GROUP)) { |
| String version = coordinates.getVersion(); |
| if (coordinates.getArtifactId().equals(COMPILE_APPINDEXING)) { |
| myAppIndexingLibVersion = version; |
| } |
| if (myHighestGmsLibVersion == null || compareVersion(version, myHighestGmsLibVersion) > 0) { |
| myHighestGmsLibVersion = version; |
| } |
| } |
| |
| for (AndroidLibrary dependency : library.getLibraryDependencies()) { |
| getDependencyVersionFromAndroidLibrary(dependency); |
| } |
| } |
| |
| /** |
| * Generates App Indexing Support in build.gradle of app module, if no such support exists. |
| * |
| * @return true if gradle myFile is changed and needs sync. |
| */ |
| @VisibleForTesting |
| boolean insertGmsCompileDependencyInGradleIfNeeded() throws ApiCreatorException { |
| GradleBuildModel buildModel = GradleBuildModel.get(myModule); |
| if (buildModel == null) { |
| throw new ApiCreatorException("Build model not found."); |
| } |
| DependenciesModel dependencies = buildModel.dependencies(); |
| String versionToUse = MINIMUM_VERSION; |
| if (myHighestGmsLibVersion != null && compareVersion(myHighestGmsLibVersion, MINIMUM_VERSION) > 0) { |
| versionToUse = myHighestGmsLibVersion; |
| } |
| |
| boolean gradleChange = false; |
| if (myAppIndexingLibVersion == null) { |
| ArtifactDependencySpec newDependency = new ArtifactDependencySpec(COMPILE_APPINDEXING, COMPILE_GMS_GROUP, versionToUse); |
| dependencies = buildModel.dependencies(); |
| dependencies.addArtifact(COMPILE, newDependency); |
| buildModel.applyChanges(); |
| gradleChange = true; |
| } |
| |
| if (myHighestGmsLibVersion != null && compareVersion(myHighestGmsLibVersion, versionToUse) < 0) { |
| List<ArtifactDependencyModel> dependencyList = dependencies.artifacts(); |
| for (ArtifactDependencyModel dependency : dependencyList) { |
| String group = dependency.group().value(); |
| if (group != null && group.equals(COMPILE_GMS_GROUP)) { |
| dependency.setVersion(versionToUse); |
| buildModel.applyChanges(); |
| gradleChange = true; |
| } |
| } |
| } |
| return gradleChange; |
| } |
| |
| /** |
| * Generates App Indexing Support in AndroidManifest.xml. |
| * |
| * @param manifestPsiFile The psi file of AndroidManifest.xml. |
| */ |
| @VisibleForTesting |
| void insertGmsVersionTagInManifestIfNeeded(@NotNull XmlFile manifestPsiFile) { |
| XmlTag root = manifestPsiFile.getRootTag(); |
| if (root != null) { |
| List<XmlTag> applications = ManifestUtils.searchXmlTagsByName(root, SdkConstants.TAG_APPLICATION); |
| for (XmlTag application : applications) { |
| if (getGmsTag(application) == null) { |
| XmlTag gms = application.createChildTag(TAG_METADATA, null, null, false); |
| gms = application.addSubTag(gms, false); |
| gms.setAttribute(SdkConstants.ATTR_NAME, SdkConstants.ANDROID_URI, ATTR_NAME_METADATA_GMS); |
| gms.setAttribute(SdkConstants.ATTR_VALUE, SdkConstants.ANDROID_URI, ATTR_VALUE_METADATA_GMS); |
| |
| XmlTag GmsTag = getGmsTag(application); |
| XmlComment comment = createXmlComment(COMMENT_IN_MANIFEST); |
| if (comment != null && GmsTag != null) { |
| application.addBefore(comment, GmsTag); |
| } |
| } |
| } |
| unlockFromPsiOperation(manifestPsiFile); |
| } |
| } |
| |
| /** |
| * Generates App Indexing API code in Java source file. |
| * Steps: |
| * 1. Generates import statements in activity class. |
| * 2. Generates GoogleApiClient variable. |
| * The method will firstly scan the code to find existing App Indexing API call to get |
| * GoogleApiClient candidate. Then verify if the candidate could be reused in the generated |
| * code - it's a class member and the initialization can be adjusted to add App Indexing API. |
| * If not, a new GoogleApiClient class member will be created. |
| * 3. Generates App Indexing API call in activity. |
| * |
| * @param deepLink The deep link for the activity. It may be nullable, because |
| * we should deal with situation where no deep link is specified |
| * in AndroidManifest.xml. |
| * @param withAppIndexingDependency If gradle will be sync later. |
| */ |
| @VisibleForTesting |
| void insertAppIndexingApiCodeInJavaFile(@Nullable String deepLink, boolean withAppIndexingDependency) { |
| insertImportStatements(withAppIndexingDependency); |
| |
| String clientName = null, clientNameCandidate = null; |
| List<PsiStatement> statementsAppIndexApi = getAppIndexApiStatement(); |
| for (PsiStatement statementAppIndexApi : statementsAppIndexApi) { |
| clientNameCandidate = getClientInAppIndexingApi(statementAppIndexApi); |
| if (clientNameCandidate != null) { |
| break; |
| } |
| } |
| if (clientNameCandidate == null) { |
| List<String> clientNames = getFieldNameByType(CLASS_GOOGLE_API_CLIENT); |
| if (!clientNames.isEmpty()) { |
| clientNameCandidate = clientNames.get(0); |
| } |
| } |
| if (clientNameCandidate != null) { |
| PsiField clientField = getFieldByName(clientNameCandidate); |
| PsiStatement clientInitStatement = null; |
| if (clientField != null && !clientField.hasInitializer()) { |
| clientInitStatement = getClientInitStatements(clientNameCandidate); |
| } |
| // Adjusts client's initialization to add AppIndexing API. |
| if (clientField != null) { |
| boolean adjustmentSucceed = false; |
| if (clientField.hasInitializer()) { |
| adjustmentSucceed = adjustClientFieldInitializerIfNeeded(clientField); |
| } |
| else if (clientInitStatement != null) { |
| adjustmentSucceed = adjustClientInitStatementIfNeeded(clientInitStatement); |
| } |
| if (adjustmentSucceed) { |
| clientName = clientNameCandidate; |
| } |
| } |
| } |
| if (clientName == null) { |
| // Creates a class member with GoogleApiClient type, and initializes it at onCreate method. |
| clientName = createGoogleApiClientField(); |
| } |
| |
| String getActionMethodCallText = insertGetActionMethod(deepLink) + "()"; |
| addOrMergeOnStart(clientName, getActionMethodCallText); |
| addOrMergeOnStop(clientName, getActionMethodCallText); |
| |
| JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(myActivity); |
| |
| unlockFromPsiOperation(myFile); |
| } |
| |
| /** |
| * Insert a method which construct Action instance and return it. Return the name of the method inserted. |
| * @param deepLink The deeplink used to construct the Action instance. |
| * @return The name of the method inserted. |
| */ |
| @NotNull |
| private String insertGetActionMethod(@Nullable String deepLink) { |
| String methodName = getUnusedMethodName(METHOD_GET_ACTION); |
| String pageName = myActivity.getName(); |
| if (pageName.endsWith("Activity")) { |
| pageName = pageName.substring(0, pageName.length() - 8); |
| } |
| String url = deepLink == null ? DEFAULT_DEEP_LINK_URL : deepLink; |
| String getActionMethod = String.format( |
| GET_ACTION_FORMAT, methodName, pageName, url, myImportClasses.get(CLASS_ACTION), myImportClasses.get(CLASS_THING)); |
| PsiMethod newGetAction = myFactory.createMethodFromText(getActionMethod, null); |
| myActivity.add(newGetAction); |
| return methodName; |
| } |
| |
| @NotNull |
| private String getUnusedMethodName(@NotNull String methodName) { |
| PsiMethod[] psiMethods = myActivity.getMethods(); |
| Set<String> usedMethodNames = Sets.newHashSet(); |
| for (PsiMethod p : psiMethods) { |
| usedMethodNames.add(p.getName()); |
| } |
| String unusedMethodName = methodName; |
| for (int suffix = 0; usedMethodNames.contains(unusedMethodName); suffix++) { |
| unusedMethodName = methodName + suffix; |
| } |
| return unusedMethodName; |
| }; |
| |
| /** |
| * Generates import statements of app indexing Java Code. |
| * |
| * @param withAppIndexingDependency If gradle will be sync later. |
| */ |
| private void insertImportStatements(boolean withAppIndexingDependency) { |
| if (!withAppIndexingDependency) { |
| // Without App Indexing dependency, App-Indexing-related classes cannot be found, |
| // so "shortenClassReferences" cannot be used. |
| for (Map.Entry<String, String> className : myImportClasses.entrySet()) { |
| if (!ImportUtils.hasOnDemandImportConflict(className.getValue(), myActivity) && |
| !hasExactImportConflict(className.getValue(), (PsiJavaFile)myFile)) { |
| insertSingleImportIfNeeded(className.getValue()); |
| className.setValue(className.getKey()); |
| } |
| } |
| } |
| } |
| |
| // Copied from ImportUtils - where it was made private in 1.5 so we can't call it directly. |
| private static boolean hasExactImportConflict(String fqName, PsiJavaFile file) { |
| final PsiImportList imports = file.getImportList(); |
| if (imports == null) { |
| return false; |
| } |
| final PsiImportStatement[] importStatements = imports.getImportStatements(); |
| final int lastDotIndex = fqName.lastIndexOf((int)'.'); |
| final String shortName = fqName.substring(lastDotIndex + 1); |
| final String dottedShortName = '.' + shortName; |
| for (final PsiImportStatement importStatement : importStatements) { |
| if (importStatement.isOnDemand()) { |
| continue; |
| } |
| final String importName = importStatement.getQualifiedName(); |
| if (importName == null) { |
| return false; |
| } |
| if (!importName.equals(fqName) && importName.endsWith(dottedShortName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Searches App Indexing API call in onStart & onStop. |
| * |
| * @return All the App Indexing API call found. |
| */ |
| @NotNull |
| private List<PsiStatement> getAppIndexApiStatement() { |
| List<PsiStatement> result = Lists.newArrayList(myStartStatements); |
| result.addAll(myViewStatements); |
| if (!result.isEmpty()) { |
| return result; |
| } |
| result.addAll(myEndStatements); |
| result.addAll(myViewEndStatements); |
| return result; |
| } |
| |
| /** |
| * Gets all assignment statements of the client in onCreate. |
| * |
| * @param clientName The GoogleApiClient name. |
| * @return All the init statements. |
| */ |
| @Nullable |
| private PsiStatement getClientInitStatements(@NotNull String clientName) { |
| if (myOnCreate != null) { |
| List<PsiStatement> statements = StatementFilter.filterCodeBlock(".build()", myOnCreate); |
| for (PsiStatement statement : statements) { |
| if (statement instanceof PsiExpressionStatement) { |
| PsiType statementType = ((PsiExpressionStatement)statement).getExpression().getType(); |
| if (statementType != null && |
| statementType.getPresentableText().equals(CLASS_GOOGLE_API_CLIENT) && |
| CharMatcher.WHITESPACE.removeFrom(statement.getText()).startsWith(clientName + "=")) { |
| return statement; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Adjusts the initializer of the client field to add App Indexing API, |
| * if there is the initializer ends with ".build()" and have not added App Indexing API. |
| * |
| * @param clientField The client field. |
| * @return true if the adjustment succeeds, i.e. the initializer has added App Indexing API. |
| */ |
| private boolean adjustClientFieldInitializerIfNeeded(@NotNull PsiField clientField) { |
| PsiExpression clientInitializer = clientField.getInitializer(); |
| if (clientInitializer != null) { |
| String oldInitializerText = clientInitializer.getText(); |
| String newInitializerText = generateApiClientInitializeString(oldInitializerText); |
| if (newInitializerText != null) { |
| if (!newInitializerText.equals(oldInitializerText)) { |
| clientField.setInitializer(myFactory.createExpressionFromText(newInitializerText, null)); |
| String commentText = String.format(COMMENT_FOR_FIELD, "\"addApi(AppIndex.API)\" "); |
| myActivity.addBefore(myFactory.createCommentFromText(commentText, null), clientField); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Adjusts the the client initializing statement to add AppIndexing API, |
| * if there is the initializer ends with ".build()" and have not added App Indexing API. |
| * |
| * @param statement The init statement of client. |
| * @return true if the adjustment succeeds, i.e. the statement has added App Indexing API. |
| */ |
| private boolean adjustClientInitStatementIfNeeded(@NotNull PsiStatement statement) { |
| String oldText = statement.getText(); |
| String newText = generateApiClientInitializeString(oldText); |
| if (newText != null) { |
| PsiElement newStatement = statement.replace(myFactory.createStatementFromText(newText, null)); |
| if (!newText.equals(oldText)) { |
| addCommentsBefore(COMMENT_BUILDER_APPINDEXAPI, myOnCreate, newStatement); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Creates a class member with GoogleApiClient type, and initializes it at onCreate method. |
| * |
| * @return The GoogleApiClient name. |
| */ |
| @NotNull |
| private String createGoogleApiClientField() { |
| String clientName = getUnusedName("client", VariableKind.FIELD); |
| PsiClassType type = myFactory.createTypeByFQClassName(myImportClasses.get(CLASS_GOOGLE_API_CLIENT)); |
| PsiField clientField = (PsiField)myActivity.add(myFactory.createField(clientName, type)); |
| myCodeStyleManager.reformat(myActivity); |
| String comment = String.format(COMMENT_FOR_FIELD, ""); |
| myActivity.addBefore(myFactory.createCommentFromText(comment, null), clientField); |
| generateGoogleApiClientInitializationInOnCreate(clientName); |
| return clientName; |
| } |
| |
| /** |
| * Generates GoogleApiClient initialization in onCreate method. |
| * Used when there is no initializing statement before. |
| * |
| * @param clientName The GoogleApiClient name. |
| */ |
| private void generateGoogleApiClientInitializationInOnCreate(@NotNull String clientName) { |
| if (myOnCreate == null) { |
| String onCreateMethod = |
| String.format(ON_CREATE_FORMAT, clientName, myImportClasses.get(CLASS_GOOGLE_API_CLIENT), myImportClasses.get(CLASS_APP_INDEX)); |
| PsiMethod newOnCreate = myFactory.createMethodFromText(onCreateMethod, null); |
| myActivity.add(newOnCreate); |
| } |
| else { |
| String initText = |
| String.format(CLIENT_FORMAT, clientName, myImportClasses.get(CLASS_GOOGLE_API_CLIENT), myImportClasses.get(CLASS_APP_INDEX)); |
| PsiElement initStatement = myOnCreate.add(myFactory.createStatementFromText(initText, null)); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnCreate, initStatement); |
| } |
| } |
| |
| /** |
| * Generates app indexing api calls (GoogleApiClient's connect & start) in onStart method. |
| * |
| * @param clientName The GoogleApiClient name. |
| * @param getActionMethodCallText The text of method call for get action. |
| */ |
| private void addOrMergeOnStart(@NotNull String clientName, @NotNull String getActionMethodCallText) { |
| if (myOnStart == null) { |
| String onStartMethod = String.format(ON_START_FORMAT, clientName, getActionMethodCallText, myImportClasses.get(CLASS_APP_INDEX)); |
| myActivity.add(myFactory.createMethodFromText(onStartMethod, null)); |
| } |
| else { |
| String connectCall = clientName + ".connect();"; |
| List<PsiStatement> connectStatements = StatementFilter.filterCodeBlock(connectCall, myOnStart); |
| // Creates connect() if needed. It should be on the top. |
| if (connectStatements.isEmpty()) { |
| PsiStatement connectStatement = myFactory.createStatementFromText(connectCall, null); |
| List<PsiStatement> superOnStartStatements = StatementFilter.filterCodeBlock("super.onStart();", myOnStart); |
| if (!superOnStartStatements.isEmpty()) { |
| // Adds connect() statement after "super.onStart();". |
| connectStatement = (PsiStatement)myOnStart.addAfter(connectStatement, superOnStartStatements.get(0)); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnStart, connectStatement); |
| } |
| else { |
| // Adds connect() statement after "{" in the code block. |
| connectStatement = (PsiStatement)myOnStart.addAfter(connectStatement, myOnStart.getFirstBodyElement()); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnStart, connectStatement); |
| } |
| } |
| String startTextHalf = String.format(APP_INDEXING_START_HALF, clientName); |
| myStartStatements = StatementFilter.filterStatements(startTextHalf, myStartStatements); |
| String viewText = String.format(APP_INDEXING_VIEW, clientName); |
| myViewStatements = StatementFilter.filterStatements(viewText, myViewStatements); |
| // Creates Action variable and AppIndex.AppIndexApi.start() at the bottom. |
| if (myStartStatements.isEmpty() && myViewStatements.isEmpty()) { |
| String startText = String.format(APP_INDEXING_START, clientName, getActionMethodCallText, myImportClasses.get(CLASS_APP_INDEX)); |
| PsiElement startStatement = myFactory.createStatementFromText(startText, null); |
| startStatement = myOnStart.add(startStatement); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnStart, startStatement); |
| } |
| } |
| } |
| |
| /** |
| * Generates app indexing api calls (GoogleApiClient's disconnect & end) in onStop method. |
| * |
| * @param clientName The GoogleApiClient name. |
| * @param getActionMethodCallText The text of method call for get action. |
| */ |
| private void addOrMergeOnStop(@NotNull String clientName, @NotNull String getActionMethodCallText) { |
| if (myOnStop == null) { |
| String onStopMethod = String.format(ON_STOP_FORMAT, clientName, getActionMethodCallText, myImportClasses.get(CLASS_APP_INDEX)); |
| myActivity.add(myFactory.createMethodFromText(onStopMethod, null)); |
| } |
| else { |
| String disconnectCall = clientName + ".disconnect();"; |
| List<PsiStatement> disconnectStatements = StatementFilter.filterCodeBlock(disconnectCall, myOnStop); |
| // Creates disconnect() if needed. It should be at the bottom. |
| if (disconnectStatements.isEmpty()) { |
| PsiElement disconnectStatement = myOnStop.add(myFactory.createStatementFromText(disconnectCall, null)); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnStop, disconnectStatement); |
| } |
| String endTextHalf = String.format(APP_INDEXING_END_HALF, clientName); |
| myEndStatements = StatementFilter.filterStatements(endTextHalf, myEndStatements); |
| String viewEndText = String.format(APP_INDEXING_VIEWEND, clientName); |
| myViewEndStatements = StatementFilter.filterStatements(viewEndText, myViewEndStatements); |
| // Creates Action variable and AppIndex.AppIndexApi.end() on the top. |
| if (myEndStatements.isEmpty() && myViewEndStatements.isEmpty()) { |
| String endText = String.format(APP_INDEXING_END, clientName, getActionMethodCallText, myImportClasses.get(CLASS_APP_INDEX)); |
| PsiStatement endStatement = myFactory.createStatementFromText(endText, null); |
| List<PsiStatement> superOnStopStatements = StatementFilter.filterCodeBlock("super.onStop();", myOnStop); |
| if (!superOnStopStatements.isEmpty()) { |
| // after "super.onStop();" |
| endStatement = (PsiStatement)myOnStop.addAfter(endStatement, superOnStopStatements.get(0)); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnStop, endStatement); |
| } |
| else { |
| // after "{" in the code block. |
| endStatement = (PsiStatement)myOnStop.addAfter(endStatement, myOnStop.getFirstBodyElement()); |
| addCommentsBefore(COMMENT_IN_JAVA, myOnStop, endStatement); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Compares two versions in the form of "12.3.456" etc. |
| * |
| * @return < 0 if version1 < version2; = 0 if version1 = version2; > 0 if version1 > version2 |
| */ |
| private static int compareVersion(@NotNull String version1, @NotNull String version2) { |
| GradleCoordinate coordinate1 = GradleCoordinate.parseVersionOnly(version1); |
| GradleCoordinate coordinate2 = GradleCoordinate.parseVersionOnly(version2); |
| return GradleCoordinate.COMPARE_PLUS_HIGHER.compare(coordinate1, coordinate2); |
| } |
| |
| /** |
| * Gets the GMS meta-data tag in the application tag. |
| */ |
| @Nullable |
| private static XmlTag getGmsTag(@NotNull XmlTag application) { |
| XmlTag[] children = application.getSubTags(); |
| for (XmlTag child : children) { |
| if (child.getName().equalsIgnoreCase(TAG_METADATA)) { |
| String tagName = child.getAttributeValue(SdkConstants.ATTR_NAME, SdkConstants.ANDROID_URI); |
| if (tagName != null && tagName.equals(ATTR_NAME_METADATA_GMS)) { |
| return child; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the client name in an app indexing API call statement. |
| */ |
| @Nullable |
| private static String getClientInAppIndexingApi(@NotNull PsiStatement statement) { |
| if (statement instanceof PsiExpressionStatement) { |
| // In the form of "AppIndex.AppIndexApi.start(mClient, viewAction);". |
| PsiElement[] children = statement.getChildren(); |
| for (PsiElement child : children) { |
| if (child instanceof PsiMethodCallExpression) { |
| String callExpression = ((PsiMethodCallExpression)child).getMethodExpression().getText(); |
| callExpression = CharMatcher.WHITESPACE.removeFrom(callExpression); |
| PsiType[] argsType = ((PsiMethodCallExpression)child).getArgumentList().getExpressionTypes(); |
| // API start / end has 2 arguments, viewEnd has 3 arguments, and view has 2. |
| // The first arg is GoogleApiClient. If not declared before used, it has a null type. |
| if ((callExpression.startsWith("AppIndex.AppIndexApi.") || |
| callExpression.startsWith("AppIndexApi") || |
| callExpression.startsWith(CLASS_APP_INDEX_FULL)) && |
| (argsType.length == 2 || argsType.length == 3 || argsType.length == 6) && |
| argsType[0] != null) { |
| PsiExpression[] args = ((PsiMethodCallExpression)child).getArgumentList().getExpressions(); |
| String clientName = args[0].getText(); |
| if (clientName != null) { |
| return clientName; |
| } |
| } |
| } |
| } |
| } |
| else if (statement instanceof PsiDeclarationStatement) { |
| // In form of "PendingResult<Status> a = AppIndex.AppIndexApi.start(mClient, viewAction);". |
| PsiElement[] children = ((PsiDeclarationStatement)statement).getDeclaredElements(); |
| for (PsiElement child : children) { |
| if (child instanceof PsiVariable) { |
| PsiExpression initializer = ((PsiVariable)child).getInitializer(); |
| if (initializer != null) { |
| // Initializer is in the form of "AppIndex.AppIndexApi.start(mClient, viewAction)". |
| PsiElement[] initChildren = initializer.getChildren(); |
| if (initChildren.length == 2 && |
| initChildren[0] instanceof PsiReferenceExpression && |
| initChildren[1] instanceof PsiExpressionList) { |
| // initChildren[0] should be in the form of "AppIndex.AppIndexApi.start". |
| // initChildren[1] should be in the form of "(mClient, ...)". |
| // args should be in the form of {"mClient", ...}. Check its length and type. |
| String referenceExpression = CharMatcher.WHITESPACE.removeFrom(initChildren[0].getText()); |
| PsiExpression[] args = ((PsiExpressionList)initChildren[1]).getExpressions(); |
| // API start / end has 2 arguments, viewEnd has 3 arguments, and view has 2. |
| // The first arg is GoogleApiClient. If not declared before used, it has a null type. |
| if ((referenceExpression.startsWith("AppIndex.AppIndexApi.") || |
| referenceExpression.startsWith("AppIndexApi.") || |
| referenceExpression.startsWith(CLASS_APP_INDEX_FULL)) && |
| (args.length == 2 || args.length == 3 || args.length == 6) && args[0].getType() != null) { |
| String clientName = args[0].getText(); |
| if (clientName != null) { |
| return clientName; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Generates a name which has not been used. |
| * Example: if "name" is used, "name2" will be returned; |
| * if "name2" is also used, "name3" will be returned; ... |
| * |
| * @param name The initial default name. |
| * @return A name based on the initial name which has not been used. |
| */ |
| @NotNull |
| private String getUnusedName(String name, VariableKind kind) { |
| JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(myProject); |
| name = javaCodeStyleManager.suggestVariableName(kind, name, null, null).names[0]; |
| |
| Set<String> usedName = getUsedVariableName(); |
| String unusedName = name; |
| int suffix = 2; |
| while (usedName.contains(unusedName)) { |
| unusedName = name + suffix; |
| suffix++; |
| } |
| return unusedName; |
| } |
| |
| /** |
| * Generates GoogleApiClient initializing string. |
| * |
| * @param statementText The original initialize string. |
| * @return The new string. |
| */ |
| @Nullable |
| private String generateApiClientInitializeString(@NotNull String statementText) { |
| int splitPoint = statementText.lastIndexOf(".build()"); |
| if (!statementText.contains(String.format(BUILDER_APPINDEXAPI, CLASS_APP_INDEX_FULL)) && |
| !statementText.contains(String.format(BUILDER_APPINDEXAPI, CLASS_APP_INDEX)) && splitPoint != -1) { |
| return statementText.substring(0, splitPoint) + |
| String.format(BUILDER_APPINDEXAPI, myImportClasses.get(CLASS_APP_INDEX)) + |
| statementText.substring(splitPoint); |
| } |
| else if (splitPoint != -1) { |
| return statementText; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns deep links of current activity in AndroidManifest.xml. |
| */ |
| @Nullable |
| @VisibleForTesting |
| String getDeepLinkOfActivity() { |
| XmlFile manifest = ManifestUtils.getAndroidManifestPsi(myModule); |
| if (manifest != null && manifest.getRootTag() != null) { |
| List<XmlTag> activityTags = ManifestUtils.searchXmlTagsByName(manifest.getRootTag(), SdkConstants.TAG_ACTIVITY); |
| for (XmlTag activityTag : activityTags) { |
| String activityName = activityTag.getAttributeValue(SdkConstants.ATTR_NAME, SdkConstants.ANDROID_URI); |
| if (activityName != null && activityName.equals("." + myActivity.getName())) { |
| List<String> deepLinks = DeepLinkUtils.getAllDeepLinks(activityTag); |
| if (!deepLinks.isEmpty()) { |
| return deepLinks.get(0); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Creates xml comment element. |
| * |
| * @param text The text form of comment. |
| * @return The created comment. |
| */ |
| @Nullable |
| private XmlComment createXmlComment(@NotNull String text) { |
| // XmlElementFactory does not provide API for creating comment. |
| // So we create a tag wrapping the comment, and extract comment from the created tag. |
| XmlElementFactory xmlElementFactory = XmlElementFactory.getInstance(myProject); |
| XmlTag commentElement = xmlElementFactory.createTagFromText("<foo>" + text + "</foo>", XMLLanguage.INSTANCE); |
| return PsiTreeUtil.getChildOfType(commentElement, XmlComment.class); |
| } |
| |
| /** |
| * Adds comments before a specific psi element. |
| * |
| * @param texts The text of the comments. |
| * "// Comment1. |
| * // Comment2." are 2 comments. |
| * Factory cannot create 2 comments together, so we split them into list of comment texts. |
| * @param element The psi element for adding comment in. |
| * @param anchor The psi element the added comment is anchored before. |
| */ |
| private void addCommentsBefore(@NotNull List<String> texts, @NotNull PsiElement element, @NotNull PsiElement anchor) { |
| myCodeStyleManager.reformat(element); |
| for (String text : texts) { |
| element.addBefore(myFactory.createCommentFromText(text, null), anchor); |
| myCodeStyleManager.reformat(element); |
| } |
| } |
| |
| /** |
| * Unlocks the file locked by PSI operation, so that the project can sync. |
| * |
| * @param file The file locked by PSI operation. |
| */ |
| private void unlockFromPsiOperation(@NotNull PsiFile file) { |
| Document doc = PsiDocumentManager.getInstance(myProject).getDocument(file); |
| if (doc != null) { |
| PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(doc); |
| } |
| } |
| |
| /** |
| * Gets the surrounding class, if it inherits from a particular super class. |
| * |
| * @param element The PsiElement for query. |
| * @param inheritedClass the name of the class to inherit from |
| * @return The surrounding class that meet the inheriting requirement. |
| */ |
| @Nullable |
| private static PsiClass getSurroundingInheritingClass(@NotNull PsiElement element, @NotNull String inheritedClass) { |
| while (element != null) { |
| if (element instanceof PsiClass) { |
| PsiClass psiClass = (PsiClass)element; |
| if (InheritanceUtil.isInheritor(psiClass, inheritedClass)) { |
| return psiClass; |
| } |
| } |
| if (element instanceof PsiFile) { |
| return null; |
| } |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the body of the method in a specific class, given the method name. |
| * |
| * @param name The method name. |
| * @param parametersTypeName The type name of the parameters in the specific method. |
| * @param psiClass The class to find method in. |
| * @return The code block body of the method, which will be null if no method is found with designated name or parameters. |
| */ |
| @Nullable |
| public static PsiCodeBlock getMethodBodyByName(@NotNull String name, |
| @NotNull List<String> parametersTypeName, |
| @NotNull PsiClass psiClass) { |
| PsiMethod[] psiMethods = psiClass.findMethodsByName(name, false); |
| for (PsiMethod psiMethod : psiMethods) { |
| PsiType[] types = psiMethod.getSignature(PsiSubstitutor.EMPTY).getParameterTypes(); |
| if (types.length != parametersTypeName.size()) { |
| continue; |
| } |
| boolean correctSignature = true; |
| for (int i = 0; i < types.length; i++) { |
| if (!types[i].getCanonicalText().equals(parametersTypeName.get(i))) { |
| correctSignature = false; |
| break; |
| } |
| } |
| if (correctSignature) { |
| return psiMethod.getBody(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets names of class members, given the type name. |
| * |
| * @param typeName The type presentable name. |
| * @return The members' names |
| */ |
| @NotNull |
| private List<String> getFieldNameByType(@NotNull String typeName) { |
| List<String> result = Lists.newArrayList(); |
| PsiField[] psiFields = myActivity.getFields(); |
| for (PsiField psiField : psiFields) { |
| if (psiField.getType().getPresentableText().equals(typeName)) { |
| result.add(psiField.getName()); |
| } |
| } |
| return result; |
| } |
| |
| @Nullable |
| private PsiField getFieldByName(@NotNull String name) { |
| PsiField[] psiFields = myActivity.getFields(); |
| for (PsiField psiField : psiFields) { |
| if (psiField.getName().equals(name)) { |
| return psiField; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Inserts a single import statement. |
| * |
| * @param className The name of the import class. |
| */ |
| private void insertSingleImportIfNeeded(@NotNull String className) { |
| PsiImportList importList = ((PsiJavaFile)myFile).getImportList(); |
| if (importList != null && !hasImportStatement(importList, className)) { |
| String dummyFileName = "_Dummy_" + className + "_." + JavaFileType.INSTANCE.getDefaultExtension(); |
| PsiJavaFile aFile = (PsiJavaFile)PsiFileFactory.getInstance(myProject) |
| .createFileFromText(dummyFileName, JavaFileType.INSTANCE, "import " + className + ";"); |
| PsiImportList dummyImportList = aFile.getImportList(); |
| if (dummyImportList != null) { |
| PsiImportStatement[] statements = dummyImportList.getImportStatements(); |
| PsiImportStatement statement = (PsiImportStatement)myCodeStyleManager.reformat(statements[0]); |
| importList.add(statement); |
| } |
| } |
| } |
| |
| /** |
| * If an class has been imported in the importList. |
| * |
| * @param importList The import list. |
| * @param className The name of the class to be imported. |
| * @return True if an class has been imported. |
| */ |
| public static boolean hasImportStatement(@NotNull PsiImportList importList, @NotNull String className) { |
| String packageName = className.substring(0, className.lastIndexOf('.')); |
| PsiImportStatement singleImport = importList.findSingleClassImportStatement(className); |
| PsiImportStatement onDemandImport = importList.findOnDemandImportStatement(packageName); |
| return singleImport != null || onDemandImport != null; |
| } |
| |
| /** |
| * Returns all the fields' and local variables' names which have already been used. |
| */ |
| @NotNull |
| private Set<String> getUsedVariableName() { |
| Set<String> usedNames = Sets.newHashSet(); |
| // name of PsiFields in PsiClass |
| PsiField[] psiFields = myActivity.getFields(); |
| for (PsiField psiField : psiFields) { |
| usedNames.add(psiField.getName()); |
| } |
| // name of PsiLocalVariables in PsiMethods in PsiClass |
| PsiMethod[] psiMethods = myActivity.getMethods(); |
| for (PsiMethod psiMethod : psiMethods) { |
| PsiCodeBlock methodBody = psiMethod.getBody(); |
| if (methodBody != null) { |
| List<PsiStatement> psiStatements = StatementFilter.filterCodeBlock("", methodBody); |
| for (PsiStatement psiStatement : psiStatements) { |
| if (psiStatement instanceof PsiDeclarationStatement) { |
| PsiElement[] declaredElements = ((PsiDeclarationStatement)psiStatement).getDeclaredElements(); |
| for (PsiElement declaredElement : declaredElements) { |
| usedNames.add(((PsiLocalVariable)declaredElement).getName()); |
| } |
| } |
| } |
| } |
| } |
| return usedNames; |
| } |
| |
| static class ApiCreatorException extends Exception { |
| public ApiCreatorException(String message) { |
| super(message); |
| } |
| } |
| } |