All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.spi.java.hints.JavaFix Maven / Gradle / Ivy

/*
 * 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.function.Function;
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.ModificationResult;
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 options;
    private final String sortText;
    private volatile Function modResult2ChangeInfo;

    /**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.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.emptyMap(), sortText);
    }

    JavaFix(CompilationInfo info, TreePath tp, Map 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.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.emptyMap());
    }

    JavaFix(TreePathHandle handle, Map options) {
        this(handle, options, null);
    }
    
    JavaFix(TreePathHandle handle, Map options, String sortText) {
        this.handle = handle;
        this.options = Collections.unmodifiableMap(new HashMap<>(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 resourceContent, Collection 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 getOptions(JavaFix jf) {
                return jf.options;
            }

            @Override
            public Fix rewriteFix(CompilationInfo info, String displayName, TreePath what, String to, Map parameters, Map> parametersMulti, Map parameterNames, Map constraints, Map 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 createSuppressWarnings(CompilationInfo compilationInfo, TreePath treePath, String... keys) {
                return ErrorDescriptionFactory.createSuppressWarnings(compilationInfo, treePath, keys);
            }

            @Override
            public List resolveDefaultFixes(HintContext ctx, Fix... provided) {
                return ErrorDescriptionFactory.resolveDefaultFixes(ctx, provided);
            }

            @Override
            public String getSortText(JavaFix jf) {
                return jf.sortText;
            }

            @Override
            public void setChangeInfoConvertor(JavaFix jf, Function modResult2ChangeInfo) {
                jf.modResult2ChangeInfo = modResult2ChangeInfo;
            }

            @Override
            public Function getChangeInfoConvertor(JavaFix jf) {
                return jf.modResult2ChangeInfo;
            }

        };
    }

    /**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 resourceContentChanges;
        private final Collection fileChanges;
        TransformationContext(WorkingCopy workingCopy, TreePath path, boolean canShowUI, Map resourceContentChanges, Collection 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(() -> {
                        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 getFileChanges() {
            return fileChanges;
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy