| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.netbeans.spi.java.hints; |
| |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.LabeledStatementTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.LiteralTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ModifiersTree; |
| import com.sun.source.tree.StatementTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.TypeElement; |
| import javax.swing.SwingUtilities; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.GeneratorUtilities; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.TreePathHandle; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.api.options.OptionsDisplayer; |
| import org.netbeans.modules.analysis.api.CodeAnalysis; |
| import org.netbeans.modules.analysis.spi.Analyzer.WarningDescription; |
| import org.netbeans.modules.java.hints.providers.spi.HintMetadata; |
| import org.netbeans.modules.java.hints.providers.spi.HintMetadata.Options; |
| import org.netbeans.modules.java.hints.spiimpl.Hacks.InspectAndTransformOpener; |
| import org.netbeans.modules.java.hints.spiimpl.SPIAccessor; |
| import org.netbeans.modules.java.hints.spiimpl.SyntheticFix; |
| import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings; |
| import org.netbeans.spi.editor.hints.ChangeInfo; |
| import org.netbeans.spi.editor.hints.EnhancedFix; |
| import org.netbeans.spi.editor.hints.ErrorDescription; |
| import org.netbeans.spi.editor.hints.Fix; |
| import org.netbeans.spi.editor.hints.LazyFixList; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.Parameters; |
| |
| /** |
| * |
| * @author Jan Lahoda |
| */ |
| public class ErrorDescriptionFactory { |
| private static final Logger LOG = Logger.getLogger(ErrorDescriptionFactory.class.getName()); |
| |
| private ErrorDescriptionFactory() { |
| } |
| |
| // public static ErrorDescription forTree(HintContext context, String text, Fix... fixes) { |
| // return forTree(context, context.getContext(), text, fixes); |
| // } |
| |
| public static ErrorDescription forTree(HintContext context, TreePath tree, String text, Fix... fixes) { |
| return forTree(context, tree.getLeaf(), text, fixes); |
| } |
| |
| public static ErrorDescription forTree(HintContext context, Tree tree, String text, Fix... fixes) { |
| int start; |
| int end; |
| int javacEnd; |
| |
| if (context.getHintMetadata().kind == Hint.Kind.INSPECTION) { |
| start = (int) context.getInfo().getTrees().getSourcePositions().getStartPosition(context.getInfo().getCompilationUnit(), tree); |
| javacEnd = (int) context.getInfo().getTrees().getSourcePositions().getEndPosition(context.getInfo().getCompilationUnit(), tree); |
| end = Math.min(javacEnd, findLineEnd(context.getInfo(), start)); |
| } else { |
| start = javacEnd = end = context.getCaretLocation(); |
| } |
| |
| if (start != (-1) && end != (-1)) { |
| if (start > end) { |
| LOG.log(Level.WARNING, "Wrong positions reported for tree (start = {0}, end = {1}): {2}", |
| new Object[] { |
| start, end, |
| tree |
| } |
| ); |
| } |
| LazyFixList fixesForED = org.netbeans.spi.editor.hints.ErrorDescriptionFactory.lazyListForFixes(resolveDefaultFixes(context, fixes)); |
| return org.netbeans.spi.editor.hints.ErrorDescriptionFactory.createErrorDescription("text/x-java:" + context.getHintMetadata().id, context.getSeverity(), text, context.getHintMetadata().description, fixesForED, context.getInfo().getFileObject(), start, end); |
| } |
| return null; |
| } |
| |
| /**Create a new {@link ErrorDescription}. Severity is automatically inferred from the {@link HintContext}, |
| * and the {@link ErrorDescription} is created to be consistent with {@link ErrorDescription}s created |
| * by the other factory methods in this class. |
| * |
| * @param context from which the {@link Severity} and other properties are inferred. |
| * @param start start of the warning |
| * @param end end of the warning |
| * @param text the warning text |
| * @param fixes one or more {@link Fix}es to show shown to the user. |
| * @return a standard {@link ErrorDescription} for use in Java source |
| * @since 1.9 |
| */ |
| public static ErrorDescription forSpan(HintContext context, int start, int end, String text, Fix... fixes) { |
| if (context.getHintMetadata().kind != Hint.Kind.INSPECTION) { |
| start = end = context.getCaretLocation(); |
| } |
| |
| if (start != (-1) && end != (-1)) { |
| LazyFixList fixesForED = org.netbeans.spi.editor.hints.ErrorDescriptionFactory.lazyListForFixes(resolveDefaultFixes(context, fixes)); |
| return org.netbeans.spi.editor.hints.ErrorDescriptionFactory.createErrorDescription("text/x-java:" + context.getHintMetadata().id, context.getSeverity(), text, context.getHintMetadata().description, fixesForED, context.getInfo().getFileObject(), start, end); |
| } |
| |
| return null; |
| } |
| |
| public static ErrorDescription forName(HintContext context, TreePath tree, String text, Fix... fixes) { |
| return forName(context, tree.getLeaf(), text, fixes); |
| } |
| |
| public static ErrorDescription forName(HintContext context, Tree tree, String text, Fix... fixes) { |
| int[] span; |
| |
| if (context.getHintMetadata().kind == Hint.Kind.INSPECTION) { |
| span = computeNameSpan(tree, context); |
| } else { |
| span = new int[] {context.getCaretLocation(), context.getCaretLocation()}; |
| } |
| |
| if (span != null && span[0] != (-1) && span[1] != (-1)) { |
| LazyFixList fixesForED = org.netbeans.spi.editor.hints.ErrorDescriptionFactory.lazyListForFixes(resolveDefaultFixes(context, fixes)); |
| return org.netbeans.spi.editor.hints.ErrorDescriptionFactory.createErrorDescription("text/x-java:" + context.getHintMetadata().id, context.getSeverity(), text, context.getHintMetadata().description, fixesForED, context.getInfo().getFileObject(), span[0], span[1]); |
| } |
| |
| return null; |
| } |
| |
| @SuppressWarnings("fallthrough") |
| private static int[] computeNameSpan(Tree tree, HintContext context) { |
| switch (tree.getKind()) { |
| case LABELED_STATEMENT: |
| return context.getInfo().getTreeUtilities().findNameSpan((LabeledStatementTree) tree); |
| case METHOD: |
| return context.getInfo().getTreeUtilities().findNameSpan((MethodTree) tree); |
| case ANNOTATION_TYPE: |
| case CLASS: |
| case ENUM: |
| case INTERFACE: |
| return context.getInfo().getTreeUtilities().findNameSpan((ClassTree) tree); |
| case VARIABLE: |
| return context.getInfo().getTreeUtilities().findNameSpan((VariableTree) tree); |
| case MEMBER_SELECT: |
| //XXX: |
| MemberSelectTree mst = (MemberSelectTree) tree; |
| int[] span = context.getInfo().getTreeUtilities().findNameSpan(mst); |
| |
| if (span == null) { |
| int end = (int) context.getInfo().getTrees().getSourcePositions().getEndPosition(context.getInfo().getCompilationUnit(), tree); |
| span = new int[] {end - mst.getIdentifier().length(), end}; |
| } |
| return span; |
| case METHOD_INVOCATION: |
| return computeNameSpan(((MethodInvocationTree) tree).getMethodSelect(), context); |
| case BLOCK: |
| Collection<? extends TreePath> prefix = context.getMultiVariables().get("$$1$"); |
| |
| if (prefix != null) { |
| BlockTree bt = (BlockTree) tree; |
| |
| if (bt.getStatements().size() > prefix.size()) { |
| return computeNameSpan(bt.getStatements().get(prefix.size()), context); |
| } |
| } |
| default: |
| int start = (int) context.getInfo().getTrees().getSourcePositions().getStartPosition(context.getInfo().getCompilationUnit(), tree); |
| if ( StatementTree.class.isAssignableFrom(tree.getKind().asInterface()) |
| && tree.getKind() != Kind.EXPRESSION_STATEMENT |
| && tree.getKind() != Kind.BLOCK) { |
| TokenSequence<?> ts = context.getInfo().getTokenHierarchy().tokenSequence(); |
| ts.move(start); |
| if (ts.moveNext()) { |
| return new int[] {ts.offset(), ts.offset() + ts.token().length()}; |
| } |
| } |
| return new int[] { |
| start, |
| Math.min((int) context.getInfo().getTrees().getSourcePositions().getEndPosition(context.getInfo().getCompilationUnit(), tree), |
| findLineEnd(context.getInfo(), start)), |
| }; |
| } |
| } |
| |
| private static int findLineEnd(CompilationInfo info, int start) { |
| String text = info.getText(); |
| |
| for (int i = start + 1; i < text.length(); i++) { |
| if (text.charAt(i) == '\n') return i; |
| } |
| |
| return text.length(); |
| } |
| |
| static List<Fix> resolveDefaultFixes(HintContext ctx, Fix... provided) { |
| List<Fix> auxiliaryFixes = new LinkedList<Fix>(); |
| HintMetadata hm = SPIAccessor.getINSTANCE().getHintMetadata(ctx); |
| |
| if (hm != null) { |
| Set<String> suppressWarningsKeys = new LinkedHashSet<String>(); |
| |
| for (String key : hm.suppressWarnings) { |
| if (key == null || key.length() == 0) { |
| break; |
| } |
| |
| suppressWarningsKeys.add(key); |
| } |
| |
| |
| auxiliaryFixes.add(new DisableConfigure(hm, true, SPIAccessor.getINSTANCE().getHintSettings(ctx))); |
| auxiliaryFixes.add(new DisableConfigure(hm, false, null)); |
| |
| if (hm.kind == Hint.Kind.INSPECTION && !hm.options.contains(Options.NO_BATCH)) { |
| auxiliaryFixes.add(new InspectFix(hm, false)); |
| if (!hm.options.contains(Options.QUERY)) { |
| auxiliaryFixes.add(new InspectFix(hm, true)); |
| } |
| } |
| |
| if (!suppressWarningsKeys.isEmpty()) { |
| auxiliaryFixes.addAll(createSuppressWarnings(ctx.getInfo(), ctx.getPath(), suppressWarningsKeys.toArray(new String[0]))); |
| } |
| |
| List<Fix> result = new LinkedList<Fix>(); |
| |
| for (Fix f : provided != null ? provided : new Fix[0]) { |
| if (f == null) continue; |
| |
| result.add(org.netbeans.spi.editor.hints.ErrorDescriptionFactory.attachSubfixes(f, auxiliaryFixes)); |
| } |
| |
| if (result.isEmpty()) { |
| result.add(org.netbeans.spi.editor.hints.ErrorDescriptionFactory.attachSubfixes(new TopLevelConfigureFix(hm), auxiliaryFixes)); |
| } |
| |
| return result; |
| } |
| |
| return Arrays.asList(provided); |
| } |
| |
| private static class DisableConfigure implements Fix, SyntheticFix { |
| private final @NonNull HintMetadata metadata; |
| private final boolean disable; |
| private final HintsSettings hintsSettings; |
| |
| DisableConfigure(@NonNull HintMetadata metadata, boolean disable, HintsSettings hintsSettings) { |
| this.metadata = metadata; |
| this.disable = disable; |
| this.hintsSettings = hintsSettings; |
| } |
| |
| @Override |
| public String getText() { |
| String displayName = metadata.displayName; |
| String key; |
| switch (metadata.kind) { |
| case INSPECTION: |
| key = disable ? "FIX_DisableHint" : "FIX_ConfigureHint"; |
| break; |
| case ACTION: |
| key = disable ? "FIX_DisableSuggestion" : "FIX_ConfigureSuggestion"; |
| break; |
| default: |
| throw new IllegalStateException(); |
| } |
| |
| return NbBundle.getMessage(ErrorDescriptionFactory.class, key, displayName); |
| } |
| |
| @Override |
| public ChangeInfo implement() throws Exception { |
| if (disable) { |
| hintsSettings.setEnabled(metadata, false); |
| //XXX: re-run hints task |
| } else { |
| OptionsDisplayer.getDefault().open("Editor/Hints/text/x-java/" + metadata.id); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (this.getClass() != obj.getClass()) { |
| return false; |
| } |
| final DisableConfigure other = (DisableConfigure) obj; |
| if (this.metadata != other.metadata && (this.metadata == null || !this.metadata.equals(other.metadata))) { |
| return false; |
| } |
| if (this.disable != other.disable) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 7; |
| hash = 43 * hash + (this.metadata != null ? this.metadata.hashCode() : 0); |
| hash = 43 * hash + (this.disable ? 1 : 0); |
| return hash; |
| } |
| |
| |
| } |
| |
| private static final class TopLevelConfigureFix extends DisableConfigure implements EnhancedFix { |
| |
| public TopLevelConfigureFix(@NonNull HintMetadata metadata) { |
| super(metadata, false, null); |
| } |
| |
| @Override |
| public CharSequence getSortText() { |
| return "\uFFFFzz"; |
| } |
| |
| } |
| |
| private static class InspectFix implements Fix, SyntheticFix { |
| private final @NonNull HintMetadata metadata; |
| private final boolean transform; |
| |
| InspectFix(@NonNull HintMetadata metadata, boolean transform) { |
| this.metadata = metadata; |
| this.transform = transform; |
| } |
| |
| @Override |
| @Messages({ |
| "DN_InspectAndTransform=Run Inspect&Transform on...", |
| "DN_Inspect=Run Inspect on..." |
| }) |
| public String getText() { |
| return transform ? Bundle.DN_InspectAndTransform() : Bundle.DN_Inspect(); |
| } |
| |
| @Override |
| public ChangeInfo implement() throws Exception { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (transform) { |
| final InspectAndTransformOpener o = Lookup.getDefault().lookup(InspectAndTransformOpener.class); |
| |
| if (o != null) { |
| o.openIAT(metadata); |
| } else { |
| //warn |
| } |
| } else { |
| CodeAnalysis.open(WarningDescription.create("text/x-java:" + metadata.id, null, null, null)); |
| } |
| } |
| }); |
| |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (this.getClass() != obj.getClass()) { |
| return false; |
| } |
| final InspectFix other = (InspectFix) obj; |
| if (this.metadata != other.metadata && (this.metadata == null || !this.metadata.equals(other.metadata))) { |
| return false; |
| } |
| if (this.transform != other.transform) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 7; |
| hash = 43 * hash + (this.metadata != null ? this.metadata.hashCode() : 0); |
| hash = 43 * hash + (this.transform ? 1 : 0); |
| return hash; |
| } |
| |
| |
| } |
| |
| /** Creates a fix, which when invoked adds @SuppresWarnings(keys) to |
| * nearest declaration. |
| * @param compilationInfo CompilationInfo to work on |
| * @param treePath TreePath to a tree. The method will find nearest outer |
| * declaration. (type, method, field or local variable) |
| * @param keys keys to be contained in the SuppresWarnings annotation. E.g. |
| * @SuppresWarnings( "key" ) or @SuppresWarnings( {"key1", "key2", ..., "keyN" } ). |
| * @throws IllegalArgumentException if keys are null or empty or id no suitable element |
| * to put the annotation on is found (e.g. if TreePath to CompilationUnit is given") |
| */ |
| static Fix createSuppressWarningsFix(CompilationInfo compilationInfo, TreePath treePath, String... keys ) { |
| Parameters.notNull("compilationInfo", compilationInfo); |
| Parameters.notNull("treePath", treePath); |
| Parameters.notNull("keys", keys); |
| |
| if (keys.length == 0) { |
| throw new IllegalArgumentException("key must not be empty"); // NOI18N |
| } |
| |
| if (!isSuppressWarningsSupported(compilationInfo)) { |
| return null; |
| } |
| |
| while (treePath.getLeaf().getKind() != Kind.COMPILATION_UNIT && !DECLARATION.contains(treePath.getLeaf().getKind())) { |
| treePath = treePath.getParentPath(); |
| } |
| |
| if (treePath.getLeaf().getKind() != Kind.COMPILATION_UNIT) { |
| return new FixImpl(TreePathHandle.create(treePath, compilationInfo), compilationInfo.getFileObject(), keys); |
| } else { |
| return null; |
| } |
| } |
| |
| /** Creates a fix, which when invoked adds @SuppresWarnings(keys) to |
| * nearest declaration. |
| * @param compilationInfo CompilationInfo to work on |
| * @param treePath TreePath to a tree. The method will find nearest outer |
| * declaration. (type, method, field or local variable) |
| * @param keys keys to be contained in the SuppresWarnings annotation. E.g. |
| * @SuppresWarnings( "key" ) or @SuppresWarnings( {"key1", "key2", ..., "keyN" } ). |
| * @throws IllegalArgumentException if keys are null or empty or id no suitable element |
| * to put the annotation on is found (e.g. if TreePath to CompilationUnit is given") |
| */ |
| static List<Fix> createSuppressWarnings(CompilationInfo compilationInfo, TreePath treePath, String... keys ) { |
| Parameters.notNull("compilationInfo", compilationInfo); |
| Parameters.notNull("treePath", treePath); |
| Parameters.notNull("keys", keys); |
| |
| if (keys.length == 0) { |
| throw new IllegalArgumentException("key must not be empty"); // NOI18N |
| } |
| |
| Fix f = createSuppressWarningsFix(compilationInfo, treePath, keys); |
| |
| if (f != null) { |
| return Collections.<Fix>singletonList(f); |
| } else { |
| return Collections.emptyList(); |
| } |
| } |
| |
| private static boolean isSuppressWarningsSupported(CompilationInfo info) { |
| //cannot suppress if there is no SuppressWarnings annotation in the platform: |
| if (info.getElements().getTypeElement("java.lang.SuppressWarnings") == null) |
| return false; |
| |
| return info.getSourceVersion().compareTo(SourceVersion.RELEASE_5) >= 0; |
| } |
| |
| private static final Set<Kind> DECLARATION = EnumSet.of(Kind.ANNOTATION_TYPE, Kind.CLASS, Kind.ENUM, Kind.INTERFACE, Kind.METHOD, Kind.VARIABLE); |
| |
| private static final class FixImpl implements Fix, SyntheticFix { |
| |
| private String keys[]; |
| private TreePathHandle handle; |
| private FileObject file; |
| |
| public FixImpl(TreePathHandle handle, FileObject file, String... keys) { |
| this.keys = keys; |
| this.handle = handle; |
| this.file = file; |
| } |
| |
| public String getText() { |
| StringBuilder keyNames = new StringBuilder(); |
| for (int i = 0; i < keys.length; i++) { |
| String string = keys[i]; |
| keyNames.append(string); |
| if ( i < keys.length - 1) { |
| keyNames.append(", "); // NOI18N |
| } |
| } |
| |
| return NbBundle.getMessage(ErrorDescriptionFactory.class, "LBL_FIX_Suppress_Waning", keyNames.toString() ); // NOI18N |
| } |
| |
| public ChangeInfo implement() throws IOException { |
| JavaSource js = JavaSource.forFileObject(file); |
| |
| js.runModificationTask(new Task<WorkingCopy>() { |
| public void run(WorkingCopy copy) throws IOException { |
| copy.toPhase(Phase.RESOLVED); //XXX: performance |
| TreePath path = handle.resolve(copy); |
| |
| while (path != null && path.getLeaf().getKind() != Kind.COMPILATION_UNIT && !DECLARATION.contains(path.getLeaf().getKind())) { |
| path = path.getParentPath(); |
| } |
| |
| if (path == null || path.getLeaf().getKind() == Kind.COMPILATION_UNIT) { |
| return ; |
| } |
| |
| Tree top = path.getLeaf(); |
| ModifiersTree modifiers = null; |
| TreePath lambdaPath = null; |
| |
| switch (top.getKind()) { |
| case ANNOTATION_TYPE: |
| case CLASS: |
| case ENUM: |
| case INTERFACE: |
| modifiers = ((ClassTree) top).getModifiers(); |
| break; |
| case METHOD: |
| modifiers = ((MethodTree) top).getModifiers(); |
| break; |
| case VARIABLE: { |
| if (path.getParentPath() != null && |
| path.getParentPath().getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { |
| // check if the variable is an implict parameter. If so, it must be turned into explicit |
| TreePath typePath = TreePath.getPath(path.getParentPath(), ((VariableTree)top).getType()); |
| if (copy.getTreeUtilities().isSynthetic(typePath)) { |
| lambdaPath = path.getParentPath(); |
| } |
| } |
| modifiers = ((VariableTree) top).getModifiers(); |
| } |
| break; |
| default: assert false : "Unhandled Tree.Kind"; // NOI18N |
| } |
| |
| if (modifiers == null) { |
| return ; |
| } |
| |
| TypeElement el = copy.getElements().getTypeElement("java.lang.SuppressWarnings"); // NOI18N |
| |
| if (el == null) { |
| return ; |
| } |
| |
| LiteralTree[] keyLiterals = new LiteralTree[keys.length]; |
| |
| for (int i = 0; i < keys.length; i++) { |
| keyLiterals[i] = copy.getTreeMaker(). |
| Literal(keys[i]); |
| } |
| |
| if (lambdaPath != null) { |
| LambdaExpressionTree let = (LambdaExpressionTree)lambdaPath.getLeaf(); |
| for (VariableTree var : let.getParameters()) { |
| TreePath typePath = TreePath.getPath(lambdaPath, var.getType()); |
| if (copy.getTreeUtilities().isSynthetic(typePath)) { |
| Tree imported = copy.getTreeMaker().Type(copy.getTrees().getTypeMirror(typePath)); |
| copy.rewrite(var.getType(), imported); |
| } |
| } |
| } |
| |
| ModifiersTree nueMods = GeneratorUtilities.get(copy).appendToAnnotationValue(modifiers, el, "value", keyLiterals); |
| |
| copy.rewrite(modifiers, nueMods); |
| } |
| }).commit(); |
| |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| final FixImpl other = (FixImpl) obj; |
| if (!Arrays.deepEquals(this.keys, other.keys)) { |
| return false; |
| } |
| if (this.handle != other.handle && (this.handle == null || !this.handle.equals(other.handle))) { |
| return false; |
| } |
| if (this.file != other.file && (this.file == null || !this.file.equals(other.file))) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 5; |
| hash = 79 * hash + Arrays.deepHashCode(this.keys); |
| hash = 79 * hash + (this.handle != null ? this.handle.hashCode() : 0); |
| hash = 79 * hash + (this.file != null ? this.file.hashCode() : 0); |
| return hash; |
| } |
| } |
| } |