blob: 5f13c140eff1b2fba0180973851f03ee64e1abc3 [file] [log] [blame]
/*
* 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.util.TreePath;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.type.TypeMirror;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl;
import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl.EnhancedJavaFixImpl;
import org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities;
import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
import org.netbeans.spi.editor.hints.ChangeInfo;
import org.netbeans.spi.editor.hints.EnhancedFix;
import org.netbeans.spi.editor.hints.Fix;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.Parameters;
/**A base class for fixes that modify Java source code. Using this class
* as a base class makes creating the fix somewhat simpler, but also supports
* running the hint in the Inspect&Transform dialog. The fix can be converted
* to {@link Fix} by means of the {@link #toEditorFix() } method.
*
* @see JavaFixUtilities for various predefined fixes.
* @author Jan Lahoda
*/
public abstract class JavaFix {
private final TreePathHandle handle;
private final Map<String, String> options;
private final String sortText;
/**Create JavaFix with the given base {@link TreePath}. The base {@link TreePath}
* will be passed back to the real implementation of the fix.
*
* @param info a {@link CompilationInfo} from which the given {@link TreePath} originates
* @param tp a {@link TreePath} that will be passed back to the
* {@link #performRewrite(org.netbeans.spi.java.hints.JavaFix.TransformationContext) } method
*/
protected JavaFix(@NonNull CompilationInfo info, @NonNull TreePath tp) {
this(info, tp, Collections.<String, String>emptyMap());
}
/**Create JavaFix with the given base {@link TreePath}. The base {@link TreePath}
* will be passed back to the real implementation of the fix.
*
* @param info a {@link CompilationInfo} from which the given {@link TreePath} originates
* @param tp a {@link TreePath} that will be passed back to the
* {@link #performRewrite(org.netbeans.spi.java.hints.JavaFix.TransformationContext) } method
* @param sortText if non-null, fix returned from {@link #toEditorFix() } will be an {@link EnhancedFix},
* and the given {@code sortText} will be returned from its {@link EnhancedFix#getSortText() }.
* @since 1.18
*/
protected JavaFix(@NonNull CompilationInfo info, @NonNull TreePath tp, @NullAllowed String sortText) {
this(TreePathHandle.create(tp, info), Collections.<String, String>emptyMap(), sortText);
}
JavaFix(CompilationInfo info, TreePath tp, Map<String, String> options) {
this(TreePathHandle.create(tp, info), options, null);
}
/**Create JavaFix with the given base {@link TreePathHandle}. The base {@link TreePathHandle}
* will be resolved and passed back to the real implementation of the fix.
*
* @param handle a {@link TreePathHandle} that will be resolved and passed back to the
* {@link #performRewrite(org.netbeans.spi.java.hints.JavaFix.TransformationContext) } method
*/
protected JavaFix(@NonNull TreePathHandle handle) {
this(handle, Collections.<String, String>emptyMap());
}
/**Create JavaFix with the given base {@link TreePathHandle}. The base {@link TreePathHandle}
* will be resolved and passed back to the real implementation of the fix.
*
* @param handle a {@link TreePathHandle} that will be resolved and passed back to the
* {@link #performRewrite(org.netbeans.spi.java.hints.JavaFix.TransformationContext) } method
* @param sortText if non-null, fix returned from {@link #toEditorFix() } will be an {@link EnhancedFix},
* and the given {@code sortText} will be returned from its {@link EnhancedFix#getSortText() }.
* @since 1.18
*/
protected JavaFix(@NonNull TreePathHandle handle, @NullAllowed String sortText) {
this(handle, Collections.<String, String>emptyMap());
}
JavaFix(TreePathHandle handle, Map<String, String> options) {
this(handle, options, null);
}
JavaFix(TreePathHandle handle, Map<String, String> options, String sortText) {
this.handle = handle;
this.options = Collections.unmodifiableMap(new HashMap<String, String>(options));
this.sortText = sortText;
}
/**The display text of the fix.
*
* @return the display text of the fix.
*/
protected abstract @NonNull String getText();
/**Do the transformations needed to implement the hint's function.
*
* @param ctx a context over which the fix should operate
* @throws Exception if something goes wrong while performing the transformation
* - will be logged by the infrastructure
*/
protected abstract void performRewrite(@NonNull TransformationContext ctx) throws Exception;
/**Convert this {@link JavaFix} into the Editor Hints {@link Fix}.
*
* @return a {@link Fix}, that when invoked, will invoke {@link #performRewrite(org.netbeans.spi.java.hints.JavaFix.TransformationContext) }
* method on this {@link JavaFix}.
*/
public final Fix toEditorFix() {
return sortText != null ? new EnhancedJavaFixImpl(this) : new JavaFixImpl(this);
}
static {
JavaFixImpl.Accessor.INSTANCE = new JavaFixImpl.Accessor() {
@Override
public String getText(JavaFix jf) {
return jf.getText();
}
@Override
public ChangeInfo process(JavaFix jf, WorkingCopy wc, boolean canShowUI, Map<FileObject, byte[]> resourceContent, Collection<? super RefactoringElementImplementation> fileChanges) throws Exception {
TreePath tp = jf.handle.resolve(wc);
if (tp == null) {
Logger.getLogger(JavaFix.class.getName()).log(Level.SEVERE, "Cannot resolve handle={0}", jf.handle);
return null;
}
GeneratorUtilities.get(wc).importComments(tp.getLeaf(), wc.getCompilationUnit());
jf.performRewrite(new TransformationContext(wc, tp, canShowUI, resourceContent, fileChanges));
return null;
}
@Override
public FileObject getFile(JavaFix jf) {
return jf.handle.getFileObject();
}
@Override
public Map<String, String> getOptions(JavaFix jf) {
return jf.options;
}
@Override
public Fix rewriteFix(CompilationInfo info, String displayName, TreePath what, String to, Map<String, TreePath> parameters, Map<String, Collection<? extends TreePath>> parametersMulti, Map<String, String> parameterNames, Map<String, TypeMirror> constraints, Map<String, String> options, String... imports) {
return JavaFixUtilities.rewriteFix(info, displayName, what, to, parameters, parametersMulti, parameterNames, constraints, options, imports);
}
@Override
public Fix createSuppressWarningsFix(CompilationInfo compilationInfo, TreePath treePath, String... keys) {
return ErrorDescriptionFactory.createSuppressWarningsFix(compilationInfo, treePath, keys);
}
@Override
public List<Fix> createSuppressWarnings(CompilationInfo compilationInfo, TreePath treePath, String... keys) {
return ErrorDescriptionFactory.createSuppressWarnings(compilationInfo, treePath, keys);
}
@Override
public List<Fix> resolveDefaultFixes(HintContext ctx, Fix... provided) {
return ErrorDescriptionFactory.resolveDefaultFixes(ctx, provided);
}
@Override
public String getSortText(JavaFix jf) {
return jf.sortText;
}
};
}
/**A context that contains a reference to a {@link WorkingCopy} through which
* modifications of Java source code can be made.
*
*/
public static final class TransformationContext {
private final WorkingCopy workingCopy;
private final TreePath path;
private final boolean canShowUI;
private final Map<FileObject, byte[]> resourceContentChanges;
private final Collection<? super RefactoringElementImplementation> fileChanges;
TransformationContext(WorkingCopy workingCopy, TreePath path, boolean canShowUI, Map<FileObject, byte[]> resourceContentChanges, Collection<? super RefactoringElementImplementation> fileChanges) {
this.workingCopy = workingCopy;
this.path = path;
this.canShowUI = canShowUI;
this.resourceContentChanges = resourceContentChanges;
this.fileChanges = fileChanges;
}
boolean isCanShowUI() {
return canShowUI;
}
/**Returns the {@link TreePath} that was passed to a {@link JavaFix} constructor.
*
* @return the {@link TreePath} that was passed to a {@link JavaFix} constructor.
*/
public @NonNull TreePath getPath() {
return path;
}
/**A {@link WorkingCopy} over which the transformation should operate.
* @return {@link WorkingCopy} over which the transformation should operate.
*/
public @NonNull WorkingCopy getWorkingCopy() {
return workingCopy;
}
/**Allows access to non-Java resources. The content of this InputStream will
* include all changes done through {@link #getResourceOutput(org.openide.filesystems.FileObject) }
* before calling this method.
*
* @param file whose content should be returned
* @return the file's content
* @throws IOException if something goes wrong while opening the file
* @throws IllegalArgumentException if {@code file} parameter is null, or
* if it represents a Java file
*/
public @NonNull InputStream getResourceContent(@NonNull FileObject file) throws IOException, IllegalArgumentException {
Parameters.notNull("file", file);
if ("text/x-java".equals(file.getMIMEType("text/x-java")))
throw new IllegalArgumentException("Cannot access Java files");
byte[] newContent = resourceContentChanges != null ? resourceContentChanges.get(file) : null;
if (newContent == null) {
final Document doc = BatchUtilities.getDocument(file);
if (doc != null) {
final String[] result = new String[1];
doc.render(new Runnable() {
@Override public void run() {
try {
result[0] = doc.getText(0, doc.getLength());
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
});
if (result[0] != null) {
ByteBuffer encoded = FileEncodingQuery.getEncoding(file).encode(result[0]);
byte[] encodedBytes = new byte[encoded.remaining()];
encoded.get(encodedBytes);
return new ByteArrayInputStream(encodedBytes);
}
}
return file.getInputStream();
} else {
return new ByteArrayInputStream(newContent);
}
}
/**Record a changed version of a file. The changes will be applied altogether with
* changes to the Java file. In Inspect&Transform, changes done through this
* method will be part of the preview.
*
* @param file whose content should be changed
* @return an {@link java.io.OutputStream} into which the new content of the file should be written
* @throws IOException if something goes wrong while opening the file
* @throws IllegalArgumentException if {@code file} parameter is null, or
* if it represents a Java file
*/
public @NonNull OutputStream getResourceOutput(@NonNull final FileObject file) throws IOException {
Parameters.notNull("file", file);
if ("text/x-java".equals(file.getMIMEType("text/x-java")))
throw new IllegalArgumentException("Cannot access Java files");
if (resourceContentChanges == null) return file.getOutputStream();
return new ByteArrayOutputStream() {
@Override public void close() throws IOException {
super.close();
resourceContentChanges.put(file, toByteArray());
}
};
}
Collection<? super RefactoringElementImplementation> getFileChanges() {
return fileChanges;
}
}
}