| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.intellij.compiler.server; |
| |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.util.ProgressIndicatorBase; |
| import com.intellij.openapi.progress.util.ProgressIndicatorUtils; |
| import com.intellij.openapi.progress.util.ReadTask; |
| import com.intellij.openapi.project.DumbService; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.*; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.concurrency.SequentialTaskExecutor; |
| import io.netty.channel.Channel; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.ide.PooledThreadExecutor; |
| import org.jetbrains.jps.api.CmdlineProtoUtil; |
| import org.jetbrains.jps.api.CmdlineRemoteProto; |
| import org.jetbrains.org.objectweb.asm.Opcodes; |
| |
| import java.util.*; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: 4/18/12 |
| */ |
| public abstract class DefaultMessageHandler implements BuilderMessageHandler { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.DefaultMessageHandler"); |
| public static final long CONSTANT_SEARCH_TIME_LIMIT = 60 * 1000L; // one minute |
| private final Project myProject; |
| private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE); |
| private volatile long myConstantSearchTime = 0L; |
| |
| protected DefaultMessageHandler(Project project) { |
| myProject = project; |
| } |
| |
| @Override |
| public void buildStarted(UUID sessionId) { |
| } |
| |
| @Override |
| public final void handleBuildMessage(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage msg) { |
| //noinspection EnumSwitchStatementWhichMissesCases |
| switch (msg.getType()) { |
| case BUILD_EVENT: |
| final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event = msg.getBuildEvent(); |
| if (event.getEventType() == CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type.CUSTOM_BUILDER_MESSAGE && event.hasCustomBuilderMessage()) { |
| final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.CustomBuilderMessage message = event.getCustomBuilderMessage(); |
| if (!myProject.isDisposed()) { |
| myProject.getMessageBus().syncPublisher(CustomBuilderMessageHandler.TOPIC).messageReceived( |
| message.getBuilderId(), message.getMessageType(), message.getMessageText() |
| ); |
| } |
| } |
| handleBuildEvent(sessionId, event); |
| break; |
| case COMPILE_MESSAGE: |
| handleCompileMessage(sessionId, msg.getCompileMessage()); |
| break; |
| case CONSTANT_SEARCH_TASK: |
| final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task = msg.getConstantSearchTask(); |
| handleConstantSearchTask(channel, sessionId, task); |
| break; |
| } |
| } |
| |
| protected abstract void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message); |
| |
| protected abstract void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event); |
| |
| private void handleConstantSearchTask(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) { |
| ProgressIndicatorUtils.scheduleWithWriteActionPriority(new ProgressIndicatorBase(), myTaskExecutor, new ReadTask() { |
| @Override |
| public void computeInReadAction(@NotNull ProgressIndicator indicator) { |
| if (DumbService.isDumb(myProject)) { |
| onCanceled(indicator); |
| } |
| else { |
| doHandleConstantSearchTask(channel, sessionId, task); |
| } |
| } |
| |
| @Override |
| public void onCanceled(@NotNull ProgressIndicator indicator) { |
| DumbService.getInstance(myProject).runWhenSmart(new Runnable() { |
| @Override |
| public void run() { |
| handleConstantSearchTask(channel, sessionId, task); |
| } |
| }); |
| } |
| }); |
| } |
| |
| private void doHandleConstantSearchTask(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) { |
| final String ownerClassName = task.getOwnerClassName(); |
| final String fieldName = task.getFieldName(); |
| final int accessFlags = task.getAccessFlags(); |
| final boolean accessChanged = task.getIsAccessChanged(); |
| final boolean isRemoved = task.getIsFieldRemoved(); |
| boolean canceled = false; |
| final Ref<Boolean> isSuccess = Ref.create(Boolean.TRUE); |
| final Set<String> affectedPaths = Collections.synchronizedSet(new HashSet<String>()); // PsiSearchHelper runs multiple threads |
| final long searchStart = System.currentTimeMillis(); |
| try { |
| if (myConstantSearchTime > CONSTANT_SEARCH_TIME_LIMIT) { |
| // skipping constant search and letting the build rebuild dependent modules |
| isSuccess.set(Boolean.FALSE); |
| LOG.debug("Total constant search time exceeded time limit for this build session"); |
| } |
| else if(isDumbMode()) { |
| // do not wait until dumb mode finishes |
| isSuccess.set(Boolean.FALSE); |
| LOG.debug("Constant search task: cannot search in dumb mode"); |
| } |
| else { |
| final String qualifiedName = ownerClassName.replace('$', '.'); |
| |
| handleCompileMessage(sessionId, CmdlineProtoUtil.createCompileProgressMessageResponse( |
| "Searching for usages of changed/removed constants for class " + qualifiedName |
| ).getCompileMessage()); |
| |
| final PsiClass[] classes = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass[]>() { |
| public PsiClass[] compute() { |
| return JavaPsiFacade.getInstance(myProject).findClasses(qualifiedName, GlobalSearchScope.allScope(myProject)); |
| } |
| }); |
| |
| try { |
| if (isRemoved) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| if (classes.length > 0) { |
| for (PsiClass aClass : classes) { |
| final boolean success = aClass.isValid() && performRemovedConstantSearch(aClass, fieldName, accessFlags, affectedPaths); |
| if (!success) { |
| isSuccess.set(Boolean.FALSE); |
| break; |
| } |
| } |
| } |
| else { |
| isSuccess.set( |
| performRemovedConstantSearch(null, fieldName, accessFlags, affectedPaths) |
| ); |
| } |
| } |
| }); |
| } |
| else { |
| if (classes.length > 0) { |
| final Collection<PsiField> changedFields = ApplicationManager.getApplication().runReadAction(new Computable<Collection<PsiField>>() { |
| public Collection<PsiField> compute() { |
| final List<PsiField> fields = new SmartList<PsiField>(); |
| for (PsiClass aClass : classes) { |
| if (!aClass.isValid()) { |
| return Collections.emptyList(); |
| } |
| final PsiField changedField = aClass.findFieldByName(fieldName, false); |
| if (changedField != null) { |
| fields.add(changedField); |
| } |
| } |
| return fields; |
| } |
| }); |
| if (changedFields.isEmpty()) { |
| isSuccess.set(Boolean.FALSE); |
| LOG.debug("Constant search task: field " + fieldName + " not found in classes " + qualifiedName); |
| } |
| else { |
| for (final PsiField changedField : changedFields) { |
| if (!accessChanged && isPrivate(accessFlags)) { |
| // optimization: don't need to search, cause may be used only in this class |
| continue; |
| } |
| affectDirectUsages(changedField, accessFlags, accessChanged, affectedPaths); |
| } |
| } |
| } |
| else { |
| isSuccess.set(Boolean.FALSE); |
| LOG.debug("Constant search task: class " + qualifiedName + " not found"); |
| } |
| } |
| } |
| catch (Throwable e) { |
| isSuccess.set(Boolean.FALSE); |
| LOG.debug("Constant search task: failed with message " + e.getMessage()); |
| } |
| } |
| } |
| catch (ProcessCanceledException e) { |
| canceled = true; |
| throw e; |
| } |
| finally { |
| myConstantSearchTime += (System.currentTimeMillis() - searchStart); |
| if (!canceled) { |
| notifyConstantSearchFinished(channel, sessionId, ownerClassName, fieldName, isSuccess, affectedPaths); |
| } |
| } |
| } |
| |
| private static void notifyConstantSearchFinished(Channel channel, |
| UUID sessionId, |
| String ownerClassName, |
| String fieldName, |
| Ref<Boolean> isSuccess, Set<String> affectedPaths) { |
| final CmdlineRemoteProto.Message.ControllerMessage.ConstantSearchResult.Builder builder = |
| CmdlineRemoteProto.Message.ControllerMessage.ConstantSearchResult.newBuilder(); |
| builder.setOwnerClassName(ownerClassName); |
| builder.setFieldName(fieldName); |
| if (isSuccess.get()) { |
| builder.setIsSuccess(true); |
| builder.addAllPath(affectedPaths); |
| LOG.debug("Constant search task: " + affectedPaths.size() + " affected files found"); |
| } |
| else { |
| builder.setIsSuccess(false); |
| LOG.debug("Constant search task: unsuccessful"); |
| } |
| channel.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType( |
| CmdlineRemoteProto.Message.ControllerMessage.Type.CONSTANT_SEARCH_RESULT).setConstantSearchResult(builder.build()).build() |
| )); |
| } |
| |
| private boolean isDumbMode() { |
| final DumbService dumbService = DumbService.getInstance(myProject); |
| boolean isDumb = dumbService.isDumb(); |
| if (isDumb) { |
| // wait some time |
| for (int idx = 0; idx < 5; idx++) { |
| try { |
| //noinspection BusyWait |
| Thread.sleep(10L); |
| } |
| catch (InterruptedException ignored) { |
| } |
| isDumb = dumbService.isDumb(); |
| if (!isDumb) { |
| break; |
| } |
| } |
| } |
| return isDumb; |
| } |
| |
| |
| private boolean performRemovedConstantSearch(@Nullable final PsiClass aClass, String fieldName, int fieldAccessFlags, final Set<String> affectedPaths) { |
| final PsiSearchHelper psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(myProject); |
| |
| final Ref<Boolean> result = new Ref<Boolean>(Boolean.TRUE); |
| final PsiFile fieldContainingFile = aClass != null? aClass.getContainingFile() : null; |
| |
| processIdentifiers(psiSearchHelper, new PsiElementProcessor<PsiIdentifier>() { |
| @Override |
| public boolean execute(@NotNull PsiIdentifier identifier) { |
| try { |
| final PsiElement parent = identifier.getParent(); |
| if (parent instanceof PsiReferenceExpression) { |
| final PsiClass ownerClass = getOwnerClass(parent); |
| if (ownerClass != null && ownerClass.getQualifiedName() != null) { |
| final PsiFile usageFile = ownerClass.getContainingFile(); |
| if (usageFile != null && !usageFile.equals(fieldContainingFile)) { |
| final VirtualFile vFile = usageFile.getOriginalFile().getVirtualFile(); |
| if (vFile != null) { |
| affectedPaths.add(vFile.getPath()); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| catch (PsiInvalidElementAccessException ignored) { |
| result.set(Boolean.FALSE); |
| LOG.debug("Constant search task: PIEAE thrown while searching of usages of removed constant"); |
| return false; |
| } |
| } |
| }, fieldName, getSearchScope(aClass, fieldAccessFlags), UsageSearchContext.IN_CODE); |
| |
| return result.get(); |
| } |
| |
| private SearchScope getSearchScope(PsiClass aClass, int fieldAccessFlags) { |
| SearchScope searchScope = GlobalSearchScope.projectScope(myProject); |
| if (aClass != null && isPackageLocal(fieldAccessFlags)) { |
| final PsiFile containingFile = aClass.getContainingFile(); |
| if (containingFile instanceof PsiJavaFile) { |
| final String packageName = ((PsiJavaFile)containingFile).getPackageName(); |
| final PsiPackage aPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName); |
| if (aPackage != null) { |
| searchScope = PackageScope.packageScope(aPackage, false); |
| searchScope = searchScope.intersectWith(aClass.getUseScope()); |
| } |
| } |
| } |
| return searchScope; |
| } |
| |
| private static boolean processIdentifiers(PsiSearchHelper helper, @NotNull final PsiElementProcessor<PsiIdentifier> processor, @NotNull final String identifier, @NotNull SearchScope searchScope, short searchContext) { |
| TextOccurenceProcessor processor1 = new TextOccurenceProcessor() { |
| @Override |
| public boolean execute(@NotNull PsiElement element, int offsetInElement) { |
| return !(element instanceof PsiIdentifier) || processor.execute((PsiIdentifier)element); |
| } |
| }; |
| return helper.processElementsWithWord(processor1, searchScope, identifier, searchContext, true, false); |
| } |
| |
| private void affectDirectUsages(final PsiField psiField, final int fieldAccessFlags, final boolean ignoreAccessScope, final Set<String> affectedPaths) throws ProcessCanceledException { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| if (psiField.isValid()) { |
| final PsiFile fieldContainingFile = psiField.getContainingFile(); |
| final Set<PsiFile> processedFiles = new HashSet<PsiFile>(); |
| if (fieldContainingFile != null) { |
| processedFiles.add(fieldContainingFile); |
| } |
| // if field is invalid, the file might be changed, so next time it is compiled, |
| // the constant value change, if any, will be processed |
| final Collection<PsiReferenceExpression> references = doFindReferences(psiField, fieldAccessFlags, ignoreAccessScope); |
| for (final PsiReferenceExpression ref : references) { |
| final PsiElement usage = ref.getElement(); |
| final PsiFile containingPsi = usage.getContainingFile(); |
| if (containingPsi != null && processedFiles.add(containingPsi)) { |
| final VirtualFile vFile = containingPsi.getOriginalFile().getVirtualFile(); |
| if (vFile != null) { |
| affectedPaths.add(vFile.getPath()); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| private Collection<PsiReferenceExpression> doFindReferences(final PsiField psiField, int fieldAccessFlags, boolean ignoreAccessScope) { |
| final SmartList<PsiReferenceExpression> result = new SmartList<PsiReferenceExpression>(); |
| |
| final SearchScope searchScope = ignoreAccessScope? GlobalSearchScope.projectScope(myProject) : getSearchScope(psiField.getContainingClass(), fieldAccessFlags); |
| |
| processIdentifiers(PsiSearchHelper.SERVICE.getInstance(myProject), new PsiElementProcessor<PsiIdentifier>() { |
| @Override |
| public boolean execute(@NotNull PsiIdentifier identifier) { |
| final PsiElement parent = identifier.getParent(); |
| if (parent instanceof PsiReferenceExpression) { |
| final PsiReferenceExpression refExpression = (PsiReferenceExpression)parent; |
| if (refExpression.isReferenceTo(psiField)) { |
| synchronized (result) { |
| // processor's code may be invoked from multiple threads |
| result.add(refExpression); |
| } |
| } |
| } |
| return true; |
| } |
| }, psiField.getName(), searchScope, UsageSearchContext.IN_CODE); |
| |
| return result; |
| } |
| |
| @Nullable |
| private static PsiClass getOwnerClass(PsiElement element) { |
| while (!(element instanceof PsiFile)) { |
| if (element instanceof PsiClass && element.getParent() instanceof PsiJavaFile) { // top-level class |
| final PsiClass psiClass = (PsiClass)element; |
| if (JspPsiUtil.isInJspFile(psiClass)) { |
| return null; |
| } |
| final PsiFile containingFile = psiClass.getContainingFile(); |
| if (containingFile == null) { |
| return null; |
| } |
| return JavaLanguage.INSTANCE.equals(containingFile.getLanguage())? psiClass : null; |
| } |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| private static boolean isPackageLocal(int flags) { |
| return (Opcodes.ACC_PUBLIC & flags) == 0 && (Opcodes.ACC_PROTECTED & flags) == 0 && (Opcodes.ACC_PRIVATE & flags) == 0; |
| } |
| |
| private static boolean isPrivate(int flags) { |
| return (Opcodes.ACC_PRIVATE & flags) != 0; |
| } |
| } |