/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* QNX Software Systems
* Markus Schorn (Wind River Systems)
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.editor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.TextEditorAction;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.IFunction;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPVariable;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IIndexFile;
import org.eclipse.cdt.core.index.IIndexInclude;
import org.eclipse.cdt.core.index.IIndexMacro;
import org.eclipse.cdt.core.index.IIndexName;
import org.eclipse.cdt.core.index.IndexFilter;
import org.eclipse.cdt.core.model.ILanguage;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfoProvider;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.IFunctionSummary;
import org.eclipse.cdt.ui.IRequiredInclude;
import org.eclipse.cdt.ui.text.ICHelpInvocationContext;
import org.eclipse.cdt.ui.text.SharedASTJob;
import org.eclipse.cdt.utils.PathUtil;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
import org.eclipse.cdt.internal.core.resources.ResourceLookup;
import org.eclipse.cdt.internal.corext.codemanipulation.AddIncludesOperation;
import org.eclipse.cdt.internal.ui.CHelpProviderManager;
import org.eclipse.cdt.internal.ui.ICHelpContextIds;
import org.eclipse.cdt.internal.ui.actions.WorkbenchRunnableAdapter;
import org.eclipse.cdt.internal.ui.util.ExceptionHandler;
/**
* Adds an include statement and, optionally, a 'using' declaration for the currently
* selected name.
*/
public class AddIncludeOnSelectionAction extends TextEditorAction {
public static boolean sIsJUnitTest = false;
private ITranslationUnit fTu;
private IProject fProject;
private String[] fIncludePath;
private IRequiredInclude[] fRequiredIncludes;
private String[] fUsingDeclarations;
public AddIncludeOnSelectionAction(ITextEditor editor) {
super(CEditorMessages.getBundleForConstructedKeys(), "AddIncludeOnSelection.", editor); //$NON-NLS-1$
CUIPlugin.getDefault().getWorkbench().getHelpSystem().setHelp(this,
ICHelpContextIds.ADD_INCLUDE_ON_SELECTION_ACTION);
}
private void insertInclude(IRequiredInclude[] includes, String[] usings, int beforeOffset) {
AddIncludesOperation op= new AddIncludesOperation(fTu, beforeOffset, includes, usings);
try {
PlatformUI.getWorkbench().getProgressService().runInUI(
PlatformUI.getWorkbench().getProgressService(),
new WorkbenchRunnableAdapter(op), op.getSchedulingRule());
} catch (InvocationTargetException e) {
ExceptionHandler.handle(e, getShell(), CEditorMessages.AddIncludeOnSelection_error_title,
CEditorMessages.AddIncludeOnSelection_insertion_failed);
} catch (InterruptedException e) {
// Do nothing. Operation has been canceled.
}
}
private static ITranslationUnit getTranslationUnit(ITextEditor editor) {
if (editor == null) {
return null;
}
return CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editor.getEditorInput());
}
private Shell getShell() {
return getTextEditor().getSite().getShell();
}
@Override
public void run() {
fTu = getTranslationUnit(getTextEditor());
if (fTu == null) {
return;
}
fProject = fTu.getCProject().getProject();
IScannerInfoProvider provider = CCorePlugin.getDefault().getScannerInfoProvider(fProject);
fIncludePath = null;
if (provider != null) {
IScannerInfo info = provider.getScannerInformation(fTu.getResource());
if (info != null) {
fIncludePath = info.getIncludePaths();
}
}
if (fIncludePath == null) {
fIncludePath = new String[0];
}
try {
final ISelection selection= getTextEditor().getSelectionProvider().getSelection();
if (selection.isEmpty() || !(selection instanceof ITextSelection)) {
return;
}
if (!validateEditorInputState()) {
return;
}
final String[] lookupName = new String[1];
SharedASTJob job = new SharedASTJob(CEditorMessages.AddIncludeOnSelection_label, fTu) {
@Override
public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) throws CoreException {
deduceInclude((ITextSelection) selection, ast, lookupName);
return Status.OK_STATUS;
}
};
job.schedule();
job.join();
if (fRequiredIncludes == null || fRequiredIncludes.length == 0 && lookupName[0].length() > 0) {
// Try contribution from plugins.
IFunctionSummary fs = findContribution(lookupName[0]);
if (fs != null) {
fRequiredIncludes = fs.getIncludes();
String ns = fs.getNamespace();
if (ns != null && ns.length() > 0) {
fUsingDeclarations = new String[] { fs.getNamespace() };
}
}
}
if (fRequiredIncludes != null && fRequiredIncludes.length >= 0) {
insertInclude(fRequiredIncludes, fUsingDeclarations, ((ITextSelection) selection).getOffset());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Extracts the includes for the given selection. This can be both used to perform
* the work as well as being invoked when there is a change.
* @param selection a text selection.
* @param ast an AST.
* @param lookupName a one-element array used to return the selected name.
*/
private void deduceInclude(ITextSelection selection, IASTTranslationUnit ast, String[] lookupName)
throws CoreException {
IASTNodeSelector selector = ast.getNodeSelector(fTu.getLocation().toOSString());
IASTName name = selector.findEnclosingName(selection.getOffset(), selection.getLength());
if (name == null) {
return;
}
char[] nameChars = name.toCharArray();
lookupName[0] = new String(nameChars);
IBinding binding = name.resolveBinding();
if (binding instanceof ICPPVariable) {
IType type = ((ICPPVariable) binding).getType();
type = SemanticUtil.getNestedType(type,
SemanticUtil.ALLCVQ | SemanticUtil.PTR | SemanticUtil.ARRAY | SemanticUtil.REF);
if (type instanceof IBinding) {
binding = (IBinding) type;
nameChars = binding.getNameCharArray();
}
}
if (nameChars.length == 0) {
return;
}
final Map<String, IncludeCandidate> candidatesMap= new HashMap<String, IncludeCandidate>();
final IIndex index = ast.getIndex();
final IndexFilter filter = IndexFilter.getDeclaredBindingFilter(ast.getLinkage().getLinkageID(), false);
List<IIndexBinding> bindings = new ArrayList<IIndexBinding>();
IIndexBinding adaptedBinding= index.adaptBinding(binding);
if (adaptedBinding == null) {
bindings.addAll(Arrays.asList(index.findBindings(nameChars, false, filter, new NullProgressMonitor())));
} else {
bindings.add(adaptedBinding);
while (adaptedBinding instanceof ICPPSpecialization) {
adaptedBinding= index.adaptBinding(((ICPPSpecialization) adaptedBinding).getSpecializedBinding());
if (adaptedBinding != null) {
bindings.add(adaptedBinding);
}
}
}
for (IIndexBinding indexBinding : bindings) {
// Replace ctor with the class itself.
if (indexBinding instanceof ICPPConstructor) {
indexBinding = indexBinding.getOwner();
}
IIndexName[] definitions= null;
// class, struct, union, enum-type, enum-item
if (indexBinding instanceof ICompositeType || indexBinding instanceof IEnumeration || indexBinding instanceof IEnumerator) {
definitions= index.findDefinitions(indexBinding);
} else if (indexBinding instanceof ITypedef || (indexBinding instanceof IFunction)) {
definitions = index.findDeclarations(indexBinding);
}
if (definitions != null) {
for (IIndexName definition : definitions) {
considerForInclusion(definition, indexBinding, index, candidatesMap);
}
if (definitions.length > 0 && adaptedBinding != null)
break;
}
}
IIndexMacro[] macros = index.findMacros(nameChars, filter, new NullProgressMonitor());
for (IIndexMacro macro : macros) {
IIndexName definition = macro.getDefinition();
considerForInclusion(definition, macro, index, candidatesMap);
}
final ArrayList<IncludeCandidate> candidates = new ArrayList<IncludeCandidate>(candidatesMap.values());
if (candidates.size() > 1) {
if (sIsJUnitTest) {
throw new RuntimeException("ambiguous input"); //$NON-NLS-1$
}
runInUIThread(new Runnable() {
public void run() {
ElementListSelectionDialog dialog=
new ElementListSelectionDialog(getShell(), new LabelProvider());
dialog.setElements(candidates.toArray());
dialog.setTitle(CEditorMessages.AddIncludeOnSelection_label);
dialog.setMessage(CEditorMessages.AddIncludeOnSelection_description);
if (dialog.open() == Window.OK) {
candidates.clear();
candidates.add((IncludeCandidate) dialog.getFirstResult());
}
}
});
}
fRequiredIncludes = null;
fUsingDeclarations = null;
if (candidates.size() == 1) {
IncludeCandidate candidate = candidates.get(0);
fRequiredIncludes = new IRequiredInclude[] { candidate.getInclude() };
IIndexBinding indexBinding = candidate.getBinding();
if (indexBinding instanceof ICPPBinding && !(indexBinding instanceof IIndexMacro)) {
// Decide what 'using' declaration, if any, should be added along with the include.
String usingDeclaration = deduceUsingDeclaration(binding, indexBinding, ast);
if (usingDeclaration != null)
fUsingDeclarations = new String[] { usingDeclaration };
}
}
}
/**
* Adds an include candidate to the <code>candidates</code> map if the file containing
* the definition is suitable for inclusion.
*/
private void considerForInclusion(IIndexName definition, IIndexBinding binding,
IIndex index, Map<String, IncludeCandidate> candidates) throws CoreException {
if (definition == null) {
return;
}
IIndexFile file = definition.getFile();
// Consider the file for inclusion only if it is not a source file,
// or a source file that was already included by some other file.
if (!isSource(getPath(file)) || index.findIncludedBy(file, 0).length > 0) {
IIndexFile representativeFile = getRepresentativeFile(file, index);
IRequiredInclude include = getRequiredInclude(representativeFile, index);
if (include != null) {
IncludeCandidate candidate = new IncludeCandidate(binding, include);
if (!candidates.containsKey(candidate.toString())) {
candidates.put(candidate.toString(), candidate);
}
}
}
}
private String deduceUsingDeclaration(IBinding source, IBinding target, IASTTranslationUnit ast) {
if (source.equals(target)) {
return null; // No using declaration is needed.
}
ArrayList<String> targetChain = getUsingChain(target);
if (targetChain.size() <= 1) {
return null; // Target is not in a namespace
}
// Check if any of the existing using declarations and directives matches
// the target.
final IASTDeclaration[] declarations= ast.getDeclarations(false);
for (IASTDeclaration declaration : declarations) {
if (declaration.isPartOfTranslationUnitFile()) {
IASTName name = null;
if (declaration instanceof ICPPASTUsingDeclaration) {
name = ((ICPPASTUsingDeclaration) declaration).getName();
if (match(name, targetChain, false)) {
return null;
}
} else if (declaration instanceof ICPPASTUsingDirective) {
name = ((ICPPASTUsingDirective) declaration).getQualifiedName();
if (match(name, targetChain, true)) {
return null;
}
}
}
}
ArrayList<String> sourceChain = getUsingChain(source);
if (sourceChain.size() >= targetChain.size()) {
int j = targetChain.size();
for (int i = sourceChain.size(); --j >= 1 && --i >= 1;) {
if (!sourceChain.get(i).equals(targetChain.get(j))) {
break;
}
}
if (j <= 0) {
return null; // Source is in the target's namespace
}
}
StringBuilder buf = new StringBuilder();
for (int i = targetChain.size(); --i >= 0;) {
if (buf.length() > 0) {
buf.append("::"); //$NON-NLS-1$
}
buf.append(targetChain.get(i));
}
return buf.toString();
}
private boolean match(IASTName name, ArrayList<String> usingChain, boolean excludeLast) {
IASTName[] names;
if (name instanceof ICPPASTQualifiedName) {
names = ((ICPPASTQualifiedName) name).getNames();
} else {
names = new IASTName[] { name };
}
if (names.length != usingChain.size() - (excludeLast ? 1 : 0)) {
return false;
}
for (int i = 0; i < names.length; i++) {
if (!names[i].toString().equals(usingChain.get(usingChain.size() - 1 - i))) {
return false;
}
}
return true;
}
/**
* Returns components of the qualified name in reverse order.
* For ns1::ns2::Name, e.g., it returns [Name, ns2, ns1].
*/
private ArrayList<String> getUsingChain(IBinding binding) {
ArrayList<String> chain = new ArrayList<String>(4);
for (; binding != null; binding = binding.getOwner()) {
String name = binding.getName();
if (binding instanceof ICPPNamespace) {
if (name.length() == 0) {
continue;
}
} else {
chain.clear();
}
chain.add(name);
}
return chain;
}
/**
* Given a header file, decides if this header file should be included directly or
* through another header file. For example, <code>bits/stl_map.h</code> is not supposed
* to be included directly, but should be represented by <code>map</code>.
* @return the header file to include.
*/
private IIndexFile getRepresentativeFile(IIndexFile headerFile, IIndex index) {
try {
if (isWorkspaceFile(headerFile.getLocation().getURI())) {
return headerFile;
}
// TODO(sprigogin): Change to ArrayDeque when Java 5 support is no longer needed.
LinkedList<IIndexFile> front = new LinkedList<IIndexFile>();
front.add(headerFile);
HashSet<IIndexFile> processed = new HashSet<IIndexFile>();
processed.add(headerFile);
while (!front.isEmpty()) {
IIndexFile file = front.remove();
// A header without an extension is a good candidate for inclusion into a C++ source file.
if (fTu.isCXXLanguage() && !hasExtension(getPath(file))) {
return file;
}
IIndexInclude[] includes = index.findIncludedBy(file, 0);
for (IIndexInclude include : includes) {
IIndexFile includer = include.getIncludedBy();
if (!processed.contains(includer)) {
URI uri = includer.getLocation().getURI();
if (isSource(uri.getPath()) || isWorkspaceFile(uri)) {
return file;
}
front.add(includer);
processed.add(includer);
}
}
}
} catch (CoreException e) {
CUIPlugin.log(e);
}
return headerFile;
}
private boolean isWorkspaceFile(URI uri) {
for (IFile file : ResourceLookup.findFilesForLocationURI(uri)) {
if (file.exists()) {
return true;
}
}
return false;
}
private boolean hasExtension(String path) {
return path.indexOf('.', path.lastIndexOf('/') + 1) >= 0;
}
private IFunctionSummary findContribution(final String name) {
final IFunctionSummary[] fs = new IFunctionSummary[1];
IRunnableWithProgress op = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
ICHelpInvocationContext context = new ICHelpInvocationContext() {
public IProject getProject() {
return fProject;
}
public ITranslationUnit getTranslationUnit() {
return fTu;
}
};
fs[0] = CHelpProviderManager.getDefault().getFunctionInfo(context, name);
}
};
try {
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(op);
} catch (InvocationTargetException e) {
ExceptionHandler.handle(e, getShell(), CEditorMessages.AddIncludeOnSelection_error_title,
CEditorMessages.AddIncludeOnSelection_help_provider_error);
} catch (InterruptedException e) {
// Do nothing. Operation has been canceled.
}
return fs[0];
}
private void runInUIThread(Runnable runnable) {
if (Display.getCurrent() != null) {
runnable.run();
} else {
Display.getDefault().syncExec(runnable);
}
}
@Override
public void update() {
ITextEditor editor = getTextEditor();
setEnabled(editor != null && getTranslationUnit(editor) != null);
}
/**
* Checks if a file is a source file (.c, .cpp, .cc, etc). Header files are not considered source files.
* @return Returns <code>true</code> if the the file is a source file.
*/
private boolean isSource(String filename) {
IContentType ct= CCorePlugin.getContentType(fProject, filename);
if (ct != null) {
String id = ct.getId();
if (CCorePlugin.CONTENT_TYPE_CSOURCE.equals(id) || CCorePlugin.CONTENT_TYPE_CXXSOURCE.equals(id)) {
return true;
}
}
return false;
}
private static String getPath(IIndexFile file) throws CoreException {
return file.getLocation().getURI().getPath();
}
/**
* Returns the RequiredInclude object to be added to the include list
* @param path - the full path of the file to include
* @return the required include
* @throws CoreException
*/
private IRequiredInclude getRequiredInclude(IIndexFile file, IIndex index) throws CoreException {
IIndexInclude[] includes;
includes = index.findIncludedBy(file);
if (includes.length > 0) {
// Let the existing includes vote. To be eligible to vote, an include
// has to be resolvable in the context of the current translation unit.
int systemIncludeVotes = 0;
String[] ballotBox = new String[includes.length];
int k = 0;
for (IIndexInclude include : includes) {
if (isResolvable(include)) {
ballotBox[k++] = include.getFullName();
if (include.isSystemInclude()) {
systemIncludeVotes++;
}
}
}
if (k != 0) {
Arrays.sort(ballotBox, 0, k);
String contender = ballotBox[0];
int votes = 1;
String winner = contender;
int winnerVotes = votes;
for (int i = 1; i < k; i++) {
if (!ballotBox[i].equals(contender)) {
contender = ballotBox[i];
votes = 1;
}
votes++;
if (votes > winnerVotes) {
winner = contender;
winnerVotes = votes;
}
}
return new RequiredInclude(winner, systemIncludeVotes * 2 >= k);
}
}
// The file has never been included before.
URI targetUri = file.getLocation().getURI();
IPath targetLocation = PathUtil.getCanonicalPath(new Path(targetUri.getPath()));
IPath sourceLocation = PathUtil.getCanonicalPath(fTu.getResource().getLocation());
boolean isSystemIncludePath = false;
IPath path = PathUtil.makeRelativePathToIncludes(targetLocation, fIncludePath);
if (path != null && ResourceLookup.findFilesForLocationURI(targetUri).length == 0) {
// A header file in the include path but outside the workspace is included with angle brackets.
isSystemIncludePath = true;
}
if (path == null) {
IPath sourceDirectory = sourceLocation.removeLastSegments(1);
if (PathUtil.isPrefix(sourceDirectory, targetLocation)) {
path = targetLocation.removeFirstSegments(sourceDirectory.segmentCount());
} else {
path = targetLocation;
}
if (targetLocation.getDevice() != null &&
targetLocation.getDevice().equalsIgnoreCase(sourceDirectory.getDevice())) {
path = path.setDevice(null);
}
if (path.isAbsolute() && path.getDevice() == null &&
ResourceLookup.findFilesForLocationURI(targetUri).length != 0) {
// The file is inside workspace. Include with a relative path.
path = PathUtil.makeRelativePath(path, sourceDirectory);
}
}
return new RequiredInclude(path.toString(), isSystemIncludePath);
}
/**
* Returns <code>true</code> if the given include can be resolved in the context of
* the current translation unit.
*/
private boolean isResolvable(IIndexInclude include) {
try {
File target = new File(include.getIncludesLocation().getURI().getPath());
String includeName = include.getFullName();
for (String dir : fIncludePath) {
if (target.equals(new File(dir, includeName))) {
return true;
}
}
if (include.isSystemInclude()) {
return false;
}
String directory = new File(fTu.getLocationURI().getPath()).getParent();
return target.equals(new File(directory, includeName).getCanonicalFile());
} catch (CoreException e) {
CUIPlugin.log(e);
return false;
} catch (IOException e) {
CUIPlugin.log(e);
return false;
}
}
/**
* Returns the fully qualified name for a given index binding.
* @param binding
* @return binding's fully qualified name
* @throws CoreException
*/
private static String getBindingQualifiedName(IIndexBinding binding) throws CoreException {
String[] qname= CPPVisitor.getQualifiedName(binding);
StringBuilder result = new StringBuilder();
boolean needSep= false;
for (String element : qname) {
if (needSep)
result.append(Keywords.cpCOLONCOLON);
result.append(element);
needSep= true;
}
return result.toString();
}
/**
* To be used by ElementListSelectionDialog for user to choose which declarations/
* definitions for "add include" when there are more than one to choose from.
*/
private static class IncludeCandidate {
private final IIndexBinding binding;
private final IRequiredInclude include;
private final String label;
public IncludeCandidate(IIndexBinding binding, IRequiredInclude include) throws CoreException {
this.binding = binding;
this.include = include;
this.label = getBindingQualifiedName(binding) + " - " + include.toString(); //$NON-NLS-1$
}
public IIndexBinding getBinding() {
return binding;
}
public IRequiredInclude getInclude() {
return include;
}
@Override
public String toString() {
return label;
}
}
private static class RequiredInclude implements IRequiredInclude {
final String includeName;
final boolean isSystem;
RequiredInclude(String includeName, boolean isSystem) {
this.includeName = includeName;
this.isSystem = isSystem;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.ui.IRequiredInclude#getIncludeName()
*/
public String getIncludeName() {
return includeName;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.ui.IRequiredInclude#isStandard()
*/
public boolean isStandard() {
return isSystem;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(includeName.length() + 2);
buf.append(isSystem ? '<' : '"');
buf.append(includeName);
buf.append(isSystem ? '>' : '"');
return buf.toString();
}
}
}