private static void checkCodeBlock( final PsiCodeBlock body, final Set<PsiField> candidates, Set<PsiField> usedFields) { try { final ControlFlow controlFlow = ControlFlowFactory.getInstance(body.getProject()) .getControlFlow(body, AllVariablesControlFlowPolicy.getInstance()); final List<PsiVariable> usedVars = ControlFlowUtil.getUsedVariables(controlFlow, 0, controlFlow.getSize()); for (PsiVariable usedVariable : usedVars) { if (usedVariable instanceof PsiField) { final PsiField usedField = (PsiField) usedVariable; if (!usedFields.add(usedField)) { candidates.remove(usedField); // used in more than one code block } } } final List<PsiReferenceExpression> readBeforeWrites = ControlFlowUtil.getReadBeforeWrite(controlFlow); for (final PsiReferenceExpression readBeforeWrite : readBeforeWrites) { final PsiElement resolved = readBeforeWrite.resolve(); if (resolved instanceof PsiField) { final PsiField field = (PsiField) resolved; PsiElement parent = body.getParent(); if (!(parent instanceof PsiMethod) || !((PsiMethod) parent).isConstructor() || field.getInitializer() == null || field.hasModifierProperty(PsiModifier.STATIC)) { candidates.remove(field); } } } } catch (AnalysisCanceledException e) { candidates.clear(); } }
private PsiElement addDeclarationWithFieldInitializerAndRetargetReferences( final PsiElementFactory elementFactory, final String localName, final PsiCodeBlock anchorBlock, final PsiElement anchor, final Set<PsiReference> refs) throws IncorrectOperationException { final PsiDeclarationStatement decl = elementFactory.createVariableDeclarationStatement( localName, myField.getType(), myField.getInitializer()); final PsiElement newDeclaration = anchorBlock.addBefore(decl, anchor); retargetReferences(elementFactory, localName, refs); return newDeclaration; }
@Override public void onReferencesBuild(RefElement refElement) { if (refElement instanceof RefClass) { final PsiClass psiClass = (PsiClass) refElement.getElement(); if (psiClass != null) { if (refElement.isEntry()) { ((RefClassImpl) refElement).setFlag(false, CAN_BE_FINAL_MASK); } PsiMethod[] psiMethods = psiClass.getMethods(); PsiField[] psiFields = psiClass.getFields(); HashSet<PsiVariable> allFields = new HashSet<PsiVariable>(); ContainerUtil.addAll(allFields, psiFields); ArrayList<PsiVariable> instanceInitializerInitializedFields = new ArrayList<PsiVariable>(); boolean hasInitializers = false; for (PsiClassInitializer initializer : psiClass.getInitializers()) { PsiCodeBlock body = initializer.getBody(); hasInitializers = true; ControlFlow flow; try { flow = ControlFlowFactory.getInstance(body.getProject()) .getControlFlow( body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); } catch (AnalysisCanceledException e) { flow = ControlFlow.EMPTY; } Collection<PsiVariable> writtenVariables = new ArrayList<PsiVariable>(); ControlFlowUtil.getWrittenVariables(flow, 0, flow.getSize(), false, writtenVariables); for (PsiVariable psiVariable : writtenVariables) { if (allFields.contains(psiVariable)) { if (instanceInitializerInitializedFields.contains(psiVariable)) { allFields.remove(psiVariable); instanceInitializerInitializedFields.remove(psiVariable); } else { instanceInitializerInitializedFields.add(psiVariable); } } } for (PsiVariable psiVariable : writtenVariables) { if (!instanceInitializerInitializedFields.contains(psiVariable)) { allFields.remove(psiVariable); } } } for (PsiMethod psiMethod : psiMethods) { if (psiMethod.isConstructor()) { PsiCodeBlock body = psiMethod.getBody(); if (body != null) { hasInitializers = true; ControlFlow flow; try { flow = ControlFlowFactory.getInstance(body.getProject()) .getControlFlow( body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); } catch (AnalysisCanceledException e) { flow = ControlFlow.EMPTY; } Collection<PsiVariable> writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, flow.getSize(), false); for (PsiVariable psiVariable : writtenVariables) { if (instanceInitializerInitializedFields.contains(psiVariable)) { allFields.remove(psiVariable); instanceInitializerInitializedFields.remove(psiVariable); } } List<PsiMethod> redirectedConstructors = HighlightControlFlowUtil.getChainedConstructors(psiMethod); if (redirectedConstructors == null || redirectedConstructors.isEmpty()) { List<PsiVariable> ssaVariables = ControlFlowUtil.getSSAVariables(flow); ArrayList<PsiVariable> good = new ArrayList<PsiVariable>(ssaVariables); good.addAll(instanceInitializerInitializedFields); allFields.retainAll(good); } else { allFields.removeAll(writtenVariables); } } } } for (PsiField psiField : psiFields) { if ((!hasInitializers || !allFields.contains(psiField)) && psiField.getInitializer() == null) { final RefFieldImpl refField = (RefFieldImpl) myManager.getReference(psiField); if (refField != null) { refField.setFlag(false, CAN_BE_FINAL_MASK); } } } } } else if (refElement instanceof RefMethod) { final RefMethod refMethod = (RefMethod) refElement; if (refMethod.isEntry()) { ((RefMethodImpl) refMethod).setFlag(false, CAN_BE_FINAL_MASK); } } }
@Nullable public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo( @NotNull PsiVariable variable, @NotNull PsiReferenceExpression expression, @NotNull Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) { if (!PsiUtil.isAccessedForWriting(expression)) return null; final PsiElement scope = variable instanceof PsiField ? variable.getParent() : variable.getParent() == null ? null : variable.getParent().getParent(); PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); if (codeBlock == null) return null; Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock); boolean alreadyAssigned = false; for (ControlFlowUtil.VariableInfo variableInfo : codeBlockProblems) { if (variableInfo.expression == expression) { alreadyAssigned = true; break; } } if (!alreadyAssigned) { if (!(variable instanceof PsiField)) return null; final PsiField field = (PsiField) variable; final PsiClass aClass = field.getContainingClass(); if (aClass == null) return null; // field can get assigned in other field initializers final PsiField[] fields = aClass.getFields(); boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC); for (PsiField psiField : fields) { PsiExpression initializer = psiField.getInitializer(); if (psiField != field && psiField.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic && initializer != null && initializer != codeBlock && !variableDefinitelyNotAssignedIn(field, initializer)) { alreadyAssigned = true; break; } } if (!alreadyAssigned) { // field can get assigned in class initializers final PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); if (enclosingConstructorOrInitializer == null || !aClass .getManager() .areElementsEquivalent( enclosingConstructorOrInitializer.getContainingClass(), aClass)) { return null; } final PsiClassInitializer[] initializers = aClass.getInitializers(); for (PsiClassInitializer initializer : initializers) { if (initializer.hasModifierProperty(PsiModifier.STATIC) == field.hasModifierProperty(PsiModifier.STATIC)) { final PsiCodeBlock body = initializer.getBody(); if (body == codeBlock) return null; try { final ControlFlow controlFlow = getControlFlow(body); if (!ControlFlowUtil.isVariableDefinitelyNotAssigned(field, controlFlow)) { alreadyAssigned = true; break; } } catch (AnalysisCanceledException e) { // incomplete code return null; } } } } if (!alreadyAssigned && !field.hasModifierProperty(PsiModifier.STATIC)) { // then check if instance field already assigned in other constructor final PsiMethod ctr = codeBlock.getParent() instanceof PsiMethod ? (PsiMethod) codeBlock.getParent() : null; // assignment to final field in several constructors threatens us only if these are linked // (there is this() call in the beginning) final List<PsiMethod> redirectedConstructors = ctr != null && ctr.isConstructor() ? JavaHighlightUtil.getChainedConstructors(ctr) : null; for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { PsiMethod redirectedConstructor = redirectedConstructors.get(j); PsiCodeBlock body = redirectedConstructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { alreadyAssigned = true; break; } } } } if (alreadyAssigned) { String description = JavaErrorMessages.message("variable.already.assigned", variable.getName()); final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(expression) .descriptionAndTooltip(description) .create(); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createDeferFinalAssignmentFix(variable, expression)); return highlightInfo; } return null; }