XMLPlugin.java
/*******************************************************************************
* Copyright (c) 2021 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.jdt.internal.corext.fix.XMLCleanUpFixCore;
/** XML cleanup processor for PDE-relevant workspace resources. */
public class XMLPlugin extends AbstractTool<XMLCandidateHit> {
private static final ILog LOG= Platform.getLog(XMLPlugin.class);
private static final String PLUGIN_ID= "sandbox_xml_cleanup"; //$NON-NLS-1$
private static final Set<String> PDE_FILE_NAMES= Set.of(
"plugin.xml", "feature.xml", "fragment.xml"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
private static final Set<String> PDE_EXTENSIONS= Set.of("exsd", "xsd"); //$NON-NLS-1$ //$NON-NLS-2$
private static final Set<String> PDE_DIRECTORIES= Set.of("OSGI-INF", "META-INF"); //$NON-NLS-1$ //$NON-NLS-2$
private final Set<IPath> processedFiles= new HashSet<>();
private WeakReference<Set<CompilationUnitRewriteOperation>> activeOperations= new WeakReference<>(null);
private boolean enableIndent;
/** Sets whether transformed markup is indented. */
public void setEnableIndent(boolean enable) {
enableIndent= enable;
}
@Override
public synchronized void find(XMLCleanUpFixCore fixcore, CompilationUnit compilationUnit,
Set<CompilationUnitRewriteOperation> operations, Set<ASTNode> nodesProcessed,
boolean createForOnlyIfVarUsed) {
beginRun(operations);
try {
IResource resource= compilationUnit.getJavaElement().getResource();
if (!(resource instanceof IFile) || !resource.exists()) {
return;
}
IProject project= resource.getProject();
project.accept(candidate -> {
if (candidate instanceof IFile file && isPDERelevantFile(file)) {
try {
processFile(fixcore, file, operations, compilationUnit);
} catch (CoreException e) {
LOG.log(e.getStatus());
}
}
return true;
});
} catch (CoreException e) {
LOG.log(new Status(IStatus.ERROR, PLUGIN_ID, "Error during XML cleanup", e)); //$NON-NLS-1$
}
}
private void beginRun(Set<CompilationUnitRewriteOperation> operations) {
if (activeOperations.get() != operations) {
activeOperations= new WeakReference<>(operations);
processedFiles.clear();
}
}
private void processFile(XMLCleanUpFixCore fixcore, IFile file,
Set<CompilationUnitRewriteOperation> operations, CompilationUnit compilationUnit)
throws CoreException {
IPath resourcePath= file.getFullPath();
if (!processedFiles.add(resourcePath)) {
return;
}
try {
XMLResourceSupport.Transformation transformation=
XMLResourceSupport.prepare(file, enableIndent, null);
if (!transformation.changed()) {
return;
}
XMLCandidateHit hit= new XMLCandidateHit(file, transformation);
hit.whileStatement= compilationUnit;
operations.add(fixcore.rewrite(hit));
LOG.log(new Status(IStatus.INFO, PLUGIN_ID,
"Queued transformation for: " + file.getFullPath())); //$NON-NLS-1$
} catch (CoreException e) {
processedFiles.remove(resourcePath);
throw e;
}
}
@Override
public void rewrite(XMLCleanUpFixCore fixcore, XMLCandidateHit hit,
CompilationUnitRewrite cuRewrite, TextEditGroup group) {
if (hit.file == null || hit.transformation == null) {
LOG.log(new Status(IStatus.WARNING, PLUGIN_ID,
"Invalid XML cleanup candidate")); //$NON-NLS-1$
return;
}
try {
XMLResourceSupport.write(hit.file, hit.transformation, null);
LOG.log(new Status(IStatus.INFO, PLUGIN_ID,
"Applied transformation to: " + hit.file.getFullPath())); //$NON-NLS-1$
} catch (CoreException e) {
LOG.log(e.getStatus());
}
}
private static boolean isPDERelevantFile(IFile file) {
String fileName= file.getName();
String extension= file.getFileExtension();
return (PDE_FILE_NAMES.contains(fileName)
|| extension != null && PDE_EXTENSIONS.contains(extension))
&& isInPDELocation(file);
}
private static boolean isInPDELocation(IFile file) {
IResource current= file.getParent();
if (current instanceof IProject) {
return true;
}
while (current != null && !(current instanceof IProject)) {
if (current instanceof IFolder folder) {
String name= folder.getName();
if (PDE_DIRECTORIES.contains(name) || "schema".equalsIgnoreCase(name)) { //$NON-NLS-1$
return true;
}
}
current= current.getParent();
}
return false;
}
@Override
public String getPreview(boolean afterRefactoring) {
if (afterRefactoring) {
return """
/* XML Cleanup - After:
* - Empty elements collapsed to self-closing
* - Whitespace optimized
*/
// <?xml version="1.0" encoding="UTF-8"?>
// <plugin>
// <extension point="org.eclipse.ui.views"/>
// <view id="my.view" name="My View"/>
// </plugin>
"""; //$NON-NLS-1$
}
return """
/* XML Cleanup - Before:
* - Empty elements with closing tags
* - Extra whitespace
*/
// <?xml version="1.0" encoding="UTF-8"?>
// <plugin>
// <extension point="org.eclipse.ui.views">
// </extension>
// <view id="my.view" name="My View">
// </view>
// </plugin>
"""; //$NON-NLS-1$
}
}