/******************************************************************************* * Copyright (c) 2007 Business Objects Software Limited and others. * All rights reserved. * This file is 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: * Business Objects Software Limited - initial API and implementation based on Eclipse 3.3 code for * /org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/JavaIndenter.java * Eclipse source is available at: http://www.eclipse.org/downloads/ *******************************************************************************/ /* * CALTextHover.java * Created: Jul 12, 2007 * By: Andrew Eisenberg */ package org.openquark.cal.eclipse.ui.text; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextHoverExtension; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.openquark.cal.caldoc.CALDocToTooltipHTMLUtilities; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.Function; import org.openquark.cal.compiler.FunctionalAgent; import org.openquark.cal.compiler.IdentifierInfo; import org.openquark.cal.compiler.IdentifierOccurrence; import org.openquark.cal.compiler.LocalFunctionIdentifier; import org.openquark.cal.compiler.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleSourceDefinition; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.ScopedEntityNamingPolicy; import org.openquark.cal.compiler.SearchManager; import org.openquark.cal.compiler.SearchResult; import org.openquark.cal.compiler.SourceMetricsManager; import org.openquark.cal.compiler.SourcePosition; import org.openquark.cal.compiler.SourceRange; import org.openquark.cal.compiler.TypeClass; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.compiler.SourceIdentifier.Category; import org.openquark.cal.eclipse.core.CALModelManager; import org.openquark.cal.eclipse.core.builder.CALBuilder; import org.openquark.cal.eclipse.ui.CALEclipseUIPlugin; import org.openquark.cal.eclipse.ui.CALUIMessages; import org.openquark.cal.eclipse.ui.actions.ActionMessages; import org.openquark.cal.eclipse.ui.actions.ActionUtilities; import org.openquark.cal.eclipse.ui.caleditor.CALEditor; import org.openquark.cal.eclipse.ui.caleditor.CALHyperlinkDetector; import org.openquark.cal.eclipse.ui.caleditor.CALSourceViewer; import org.openquark.cal.eclipse.ui.caleditor.PartiallySynchronizedDocument; import org.openquark.cal.eclipse.ui.util.CoreUtility; import org.openquark.cal.services.ProgramModelManager; import org.openquark.util.Pair; /** * Used to provide information to the hover control. * * @author GMcClement * @author Andrew Eisenberg */ public class CALTextHover implements ITextHover, ITextHoverExtension{ /** * This is a hack. When the users presses F2 and if previously the hover showed source code * then the source code should be shown even though the hover for not-shift F2 is called. */ private static boolean showingSourceCode = false; /** * When this is used for a tooltip hover the offset override should be updated. Then * when the F2 key is pressed the browser window will ignore the cursor position and * use the position of the tool tip. If this is used by the ShowTooltipDescription action * then the override will not be used instead the cursor position should be used. */ private boolean updateOffset; private final int stateMask; /** * This field remembers whether the text to be displayed should be shown in a browser * (ie- true, it's HTML) or shown in a text viewer (ie- false, it's source code) */ protected boolean showInBrowser; /** * this can be null if the associated CALSourceViewer is not associated * with a CALEditor. This is the case for Embedded CAL */ private CALEditor textEditor; public CALTextHover(boolean updateOffset, int stateMask){ this.updateOffset = updateOffset; this.stateMask = stateMask; } public CALTextHover(boolean updateOffset, int stateMask, CALEditor editor){ this(updateOffset, stateMask); this.textEditor = editor; } public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { // The type info has to be loaded for this to work if (!CALBuilder.isEnabled() || !CoreUtility.calBuilderWasInitialized()){ return null; } final IDocument currentDocument = getDocument(); if (currentDocument != null) { final PartiallySynchronizedDocument psd = (PartiallySynchronizedDocument) currentDocument; final IDocument document = psd.getOriginalDocument(); final int currentOffset = hoverRegion.getOffset(); final int offset = psd.getOriginalOffset(currentOffset); // in new text so there will be no hover information if (offset == -1){ return null; } try { final int firstLine = document.getLineOfOffset(offset); final int column = CoreUtility.getColumn(firstLine, offset, document); CALModelManager cmm = CALModelManager.getCALModelManager(); ModuleName moduleName; try{ moduleName = getModuleName(cmm); } catch(IllegalArgumentException ex){ // CAL File is not in the correct spot in the hierarchy so there // is no type information available. Don't show an error message // since this is the wrong thread. return null; } ModuleSourceDefinition msd = cmm.getModuleSourceDefinition(moduleName); if (!isSyntheticModule() && msd == null) { // hmmm, we are hovering over text in a module but cannot find the module. textEditor.getViewer().getTextWidget().getDisplay().asyncExec(new Runnable() { // ensure we are executing in the proper thread public void run() { showErrorOnStatusLine(ActionMessages.error_messageBadSelection_noTypeInformation_CAL); } }); showingSourceCode = false; return null; } /* * If the compile failed then the metrics info is not available so bail early. */ if (!isSyntheticModule() && !cmm.getProgramModelManager().hasModuleInProgram(moduleName)){ // Only show the message if the user presses selects "Show Tooltip Description" // explicitly not when the hover text is shown. if (!updateOffset) { showErrorOnStatusLine(ActionMessages.OpenAction_error_noSourceCodeMetrics); } showingSourceCode = false; return null; } CompilerMessageLogger messageLogger = new MessageLogger(); SourceMetricsManager sourceMetrics = cmm.getSourceMetrics(); SearchManager searchManager = cmm.getSearchManager(); // override the offset with the offset of the hover text instead of the cursor position // so that if F2 is pressed the correct symbol will be found. if (updateOffset) { final CALSourceViewer calSourceViewer = (CALSourceViewer) textViewer; calSourceViewer.setOffset(offset); final StyledText textWidget = textViewer.getTextWidget(); // Clear the override textWidget.getDisplay().syncExec( new Runnable(){ public void run(){ if (!textWidget.isDisposed()){ textWidget.addListener(SWT.Hide, new Listener(){ public void handleEvent(Event event) { calSourceViewer.setOffset(-1); } }); } } } ); } // !updateOffset means that F2 is pressed. So (showingSourceCode && !updateOffset) // means that the hover is showing source code currently and F2 is pressed. if (stateMask == SWT.CONTROL|| stateMask == SWT.SHIFT || (showingSourceCode && !updateOffset)){ if (stateMask == SWT.SHIFT || stateMask == SWT.CONTROL){ showingSourceCode = true; } else{ showingSourceCode = false; } return findSourceCode(firstLine, column, moduleName, messageLogger, sourceMetrics); } else{ showingSourceCode = false; return findCALDoc(psd, currentOffset, moduleName, messageLogger, searchManager); } } catch (BadLocationException e) { // will only happen on concurrent modification CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ showingSourceCode = false; return null; } } else{ showingSourceCode = false; return null; } } /** * When not associated with a CALEditor, then there is no status line * to show the error on, so ignore. */ private void showErrorOnStatusLine(String errorMsg) { if (textEditor != null) { CoreUtility.showErrorOnStatusLine(textEditor, errorMsg); } } /** * Method getFunction. * @return Function the local function or null if the module does not define a function with the given name. */ public Function getFunction(ModuleTypeInfo mti, QualifiedName qualifiedName){ QualifiedName qn; String topLevelFunctionName; String localFunctionName; int index; { final String symbolName = qualifiedName.getUnqualifiedName(); // functionName@lfi1@lfi2@index // ^--- positionOfSeparator // ^--- startOfIndex final int positionOfSeparator = symbolName.indexOf('@'); if (positionOfSeparator == -1){ return null; } final int startOfIndex = symbolName.lastIndexOf('@'); if (startOfIndex == positionOfSeparator){ return null; } topLevelFunctionName = symbolName.substring(0, positionOfSeparator); localFunctionName = symbolName.substring(positionOfSeparator+1, startOfIndex); if (localFunctionName.length() == 0){ return null; } final String indexString = symbolName.substring(startOfIndex + 1); if (indexString.length() == 0){ return null; } index = Integer.parseInt(indexString); qn = QualifiedName.make(qualifiedName.getModuleName(), topLevelFunctionName); } final Function function = mti.getFunction(topLevelFunctionName); if (function == null){ return null; } return function.getLocalFunction(qn, localFunctionName, index); } /** * Find the source code that defines the symbol at the given position. */ private String findSourceCode(final int firstLine, final int column, ModuleName moduleName, CompilerMessageLogger messageLogger, SourceMetricsManager sourceMetrics) { SearchResult.Precise target = searchForIdentifier(firstLine, column, moduleName, messageLogger, sourceMetrics); if (target == null) { return null; } showInBrowser = false; final List<SearchResult> definitions = sourceMetrics.findDefinition(target, true, messageLogger); if (definitions.size() > 0){ SearchResult.Precise result = (SearchResult.Precise) definitions.get(0); final String sourceText = CALModelManager.getCALModelManager().getModuleSource(result.getName().getModuleName()); String typeDeclarationText = ""; if (result.getName() instanceof QualifiedName){ final QualifiedName functionName = (QualifiedName) result.getName(); final SearchResult.Precise typeDeclarationResult = sourceMetrics.findTypeDeclaration(functionName, messageLogger); // if the type declaration is in the source code if (typeDeclarationResult != null){ typeDeclarationText = typeDeclarationResult.getSourceRange().getSelection(sourceText) + ";\n"; } else{ // The type declaration is not in the source code so calculate it. // calculate the type info final ModuleTypeInfo moduleTypeInfo = CALModelManager.getCALModelManager().getModuleTypeInfo(result.getName().getModuleName()); Function function = moduleTypeInfo.getFunction(functionName.getUnqualifiedName()); if (function == null){ // check for local function function = getFunction(moduleTypeInfo, functionName); } // Don't show the type for foreign functions since the signature has it already if (function != null && function.getForeignFunctionInfo() == null && !function.isPrimitive()){ typeDeclarationText = function.getUnqualifiedDisplayName() + " :: " + function.getTypeExpr().toString(true, new ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous(moduleTypeInfo)) + "; // (inferred)\n"; } } } final String definingText; final Category category = result.getCategory(); if (category == Category.DATA_CONSTRUCTOR){ definingText = result.getSourceRange().getSelection(sourceText); } else{ definingText = getSelectionAndTrailingSemiColon(result.getSourceRange(), sourceText); } return typeDeclarationText + tidyUp(definingText, result.getSourceRange()); } else{ return null; } } /** * finds the symbol at the particular line and column. * * subclasses that are not associated with a CALEditor can override * @param firstLine * @param column * @param moduleName * @param messageLogger * @param sourceMetrics * @return the symbol at the given line and column */ protected SearchResult.Precise searchForIdentifier(final int firstLine, final int column, ModuleName moduleName, CompilerMessageLogger messageLogger, SourceMetricsManager sourceMetrics) { SearchResult.Precise[] results = sourceMetrics.findSymbolAt(moduleName, firstLine+1, column+1, messageLogger); if (results == null || results.length != 1) { return null; } SearchResult.Precise target = results[0]; return target; } /** * Gets the source text and any trailing semi-colons. This is a hack that will be removed once * the source ranges of source model elements have the correct values. * TODO remove this function when the source model is fixed. * @param sourceText sourceText that the source range refers to * @return the text in the sourceText string covered by this source range. */ public String getSelectionAndTrailingSemiColon(SourceRange sourceRange, String sourceText){ final int startIndex = sourceRange.getStartSourcePosition().getPosition(sourceText); int endIndex = sourceRange.getEndSourcePosition().getPosition(sourceText); // move the end index past the trailing semi-colons // (space | tab | newline | semicolon)* (semicolon) final int lastIndex = sourceText.length(); int lastSemicolon = endIndex; while(endIndex < lastIndex){ final char ch = sourceText.charAt(endIndex); if (Character.isWhitespace(ch)){ // continue } else if (ch == ';'){ lastSemicolon = endIndex; } else{ break; } ++endIndex; } return sourceText.substring(startIndex, lastSemicolon+1); } /** * The source code can come in indented. I want to remove the indentation since this is pointless * For example * * public "char" * public Char deriving Eq, Ord, * Inputable, Outputable; * * will be converted to * * public "char" * public Char deriving Eq, Ord, * Inputable, Outputable; * * @param sourceCode the source code to tidy up. * @return the tidied up source code. */ private String tidyUp(String sourceCode, SourceRange sourceRange){ String[] lines = sourceCode.split("\\n"); final int length = lines.length; int min = sourceRange.getStartColumn() - 1; for(int i = 1; i < length; ++i){ String line = lines[i]; final int nLeadingSpaces = getNLeadingSpaces(line); if (nLeadingSpaces < min && (nLeadingSpaces != line.length())){ min = nLeadingSpaces; } } final StringBuilder tidiedCode = new StringBuilder(); // The first line is always moved over to the start tidiedCode.append(lines[0].substring(getNLeadingSpaces(lines[0]))); tidiedCode.append('\n'); // For the rest of the lines remove the same leading blanks. for(int i = 1; i < length; ++i){ // Don't substring the all blank lines if (lines[i].length() > min){ tidiedCode.append(lines[i].substring(min)); } tidiedCode.append('\n'); } return tidiedCode.toString(); } private int getNLeadingSpaces(String string){ final int length = string.length(); for(int i = 0; i < length; ++i){ if (string.charAt(i) != ' '){ return i; } } return length; } /** * Retrieve the CALdoc for the selected symbol if any. * @param psd * @param currentOffset * @param moduleName Name of the module to search * @param messageLogger * @param searchManager * @return the CALDoc for the symbol at the given position. If no CALDoc exists, null will be returned. * @throws BadLocationException */ private String findCALDoc(final PartiallySynchronizedDocument psd, final int currentOffset, ModuleName moduleName, CompilerMessageLogger messageLogger, SearchManager searchManager) throws BadLocationException { final Pair<SourcePosition, SourcePosition> posAndPosToTheLeft = CALHyperlinkDetector.getCurrentPositionAndPositionToTheLeft(psd, currentOffset, moduleName); // current position is in new code so no hovertext is available if (posAndPosToTheLeft == null){ return null; } IdentifierOccurrence<?> result = searchManager.findSymbolAt(moduleName, posAndPosToTheLeft.fst(), posAndPosToTheLeft.snd(), messageLogger); if (result == null) { return null; } showInBrowser = true; return findCALDocForOccurrence(moduleName, result); } protected String findCALDocForOccurrence(ModuleName currentModuleName, IdentifierOccurrence<?> occurrence) { ProgramModelManager programModelManager = CALModelManager.getCALModelManager().getProgramModelManager(); IdentifierInfo name = occurrence.getIdentifierInfo(); if (name instanceof IdentifierInfo.Module){ final ModuleTypeInfo resultModuleTypeInfo = programModelManager.getModuleTypeInfo(((IdentifierInfo.Module)name).getResolvedName()); if (resultModuleTypeInfo == null){ return ""; } else{ return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfModule( programModelManager, resultModuleTypeInfo); } } else if (name instanceof IdentifierInfo.TypeVariable) { return CALDocToTooltipHTMLUtilities.getHTMLForTypeVariable(programModelManager, currentModuleName, (IdentifierInfo.TypeVariable)name); } else if (name instanceof IdentifierInfo.RecordFieldName) { return CALDocToTooltipHTMLUtilities.getHTMLForRecordFieldName(programModelManager, currentModuleName, (IdentifierInfo.RecordFieldName)name); } else if (name instanceof IdentifierInfo.DataConsFieldName) { IdentifierInfo.DataConsFieldName fieldName = (IdentifierInfo.DataConsFieldName)name; if (fieldName.getAssociatedDataConstructors().size() == 0) { throw new IllegalStateException(); } ModuleName dataConsModule = fieldName.getFirstAssociatedDataConstructor().getResolvedName().getModuleName(); ModuleTypeInfo resultModuleTypeInfo = programModelManager.getModuleTypeInfo(dataConsModule); if (resultModuleTypeInfo == null) { return null; } List<DataConstructor> dataConsList = new ArrayList<DataConstructor>(); for (final IdentifierInfo.TopLevel.DataCons assocDataCons : fieldName.getAssociatedDataConstructors()) { DataConstructor dataCons = resultModuleTypeInfo.getDataConstructor(assocDataCons.getResolvedName().getUnqualifiedName()); if (dataCons != null){ dataConsList.add(dataCons); } } if (dataConsList.isEmpty()) { return null; } return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfDataConsFieldName(programModelManager, dataConsList, fieldName); } else if (name instanceof IdentifierInfo.Local) { ModuleTypeInfo resultModuleTypeInfo = programModelManager.getModuleTypeInfo(currentModuleName); if (resultModuleTypeInfo == null) { return null; } // Look for CALDoc for the various kinds of local identifiers if (name instanceof IdentifierInfo.Local.Function){ IdentifierInfo.Local.Parameter.Function localName = (IdentifierInfo.Local.Parameter.Function)name; final LocalFunctionIdentifier localFunctionIdentifier = localName.getLocalFunctionIdentifier(); Function topLevelFunction = resultModuleTypeInfo.getFunction(localFunctionIdentifier.getToplevelFunctionName().getUnqualifiedName()); if (topLevelFunction != null) { Function localFunction = topLevelFunction.getLocalFunction(localFunctionIdentifier); if (localFunction != null) { return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfLocalFunction(programModelManager, localFunction, localName); } } } else if (name instanceof IdentifierInfo.Local.PatternMatchVariable) { IdentifierInfo.Local.Parameter.PatternMatchVariable localName = (IdentifierInfo.Local.Parameter.PatternMatchVariable)name; final LocalFunctionIdentifier localFunctionIdentifier = localName.getLocalFunctionIdentifier(); Function topLevelFunction = resultModuleTypeInfo.getFunction(localFunctionIdentifier.getToplevelFunctionName().getUnqualifiedName()); if (topLevelFunction != null) { Function localFunction = topLevelFunction.getLocalFunction(localFunctionIdentifier); if (localFunction != null) { return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfLocalPatternMatchVar(programModelManager, localFunction, localName); } } } else if (name instanceof IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod) { IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod paramName = (IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod)name; FunctionalAgent function = resultModuleTypeInfo.getFunctionOrClassMethod(paramName.getAssociatedFunction().getResolvedName().getUnqualifiedName()); if (function != null){ return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfFunctionParameter(programModelManager, function, paramName); } } else if (name instanceof IdentifierInfo.Local.Parameter.LocalFunction) { IdentifierInfo.Local.Parameter.LocalFunction paramName = (IdentifierInfo.Local.Parameter.LocalFunction)name; final LocalFunctionIdentifier localFunctionIdentifier = paramName.getAssociatedFunction().getLocalFunctionIdentifier(); Function topLevelFunction = resultModuleTypeInfo.getFunction(localFunctionIdentifier.getToplevelFunctionName().getUnqualifiedName()); if (topLevelFunction != null) { Function localFunction = topLevelFunction.getLocalFunction(localFunctionIdentifier); if (localFunction != null) { return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfFunctionParameter(programModelManager, localFunction, paramName); } } } // In all other cases, just return a simple tooltip with the kind of identifier and name displayed return CALDocToTooltipHTMLUtilities.getHTMLForSimpleLocalVariable(programModelManager, currentModuleName, (IdentifierInfo.Local)name); } else if (name instanceof IdentifierInfo.TopLevel){ QualifiedName resultQualifiedName = ((IdentifierInfo.TopLevel)name).getResolvedName(); ModuleTypeInfo resultModuleTypeInfo = programModelManager.getModuleTypeInfo(resultQualifiedName.getModuleName()); if (resultModuleTypeInfo == null) { return null; } // Look for CALDoc for the various kinds of top-level identifiers // Functions if (name instanceof IdentifierInfo.TopLevel.FunctionOrClassMethod){ FunctionalAgent function = resultModuleTypeInfo.getFunctionOrClassMethod(resultQualifiedName.getUnqualifiedName()); if (function != null){ return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfScopedEntity(programModelManager, function); } } // Type constructors if (name instanceof IdentifierInfo.TopLevel.TypeCons){ TypeConstructor typeCons = resultModuleTypeInfo.getTypeConstructor(resultQualifiedName.getUnqualifiedName()); if (typeCons != null){ return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfScopedEntity(programModelManager, typeCons); } } // Data Constructors if (name instanceof IdentifierInfo.TopLevel.DataCons){ DataConstructor dataCons = resultModuleTypeInfo.getDataConstructor(resultQualifiedName.getUnqualifiedName()); if (dataCons != null){ return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfScopedEntity(programModelManager, dataCons); } } // Type Classes if (name instanceof IdentifierInfo.TopLevel.TypeClass){ TypeClass typeClass = resultModuleTypeInfo.getTypeClass(resultQualifiedName.getUnqualifiedName()); if (typeClass != null){ return CALDocToTooltipHTMLUtilities.getHTMLForCALDocCommentOfScopedEntity(programModelManager, typeClass); } } } // to Do: doci, docim return null; } public IRegion getHoverRegion(ITextViewer textViewer, int offset) { Point selection = textViewer.getSelectedRange(); if (selection.x <= offset && offset < selection.x + selection.y) { return new Region(selection.x, selection.y); } return new Region(offset, 0); } /** * {@inheritDoc} */ public IInformationControlCreator getHoverControlCreator() { return new IInformationControlCreator() { public IInformationControl createInformationControl(Shell parent) { int shellStyle = SWT.TOOL | SWT.NO_TRIM; int style = SWT.NONE; if (showInBrowser && BrowserInformationControl.isAvailable(parent)) { // Show in HTML browser return new BrowserInformationControl(parent, shellStyle, style, CALUIMessages.GetHoverFocus + " " + CALUIMessages.GetCodeHoverAffordance); } else { // Show in text viewer return new CALSourceInformationControl(parent, shellStyle, style, CALUIMessages.GetHoverFocus); } } }; } /** * Subclasses not associated with a CALEditor can override * @param cmm * @return the module name of the CAL code being hovered over */ protected ModuleName getModuleName(CALModelManager cmm) { return CALModelManager.getCALModelManager().getModuleName(textEditor.getStorage()); } /** * Subclasses that do are not tied to a CALEditor can override * @return the document of the hover */ protected IDocument getDocument() { return ActionUtilities.getDocument(textEditor); } /** * Override if the module used in the hover does not exist in the CALModelManager * @return true if the module does not exist in the CALModelManager, false if it does */ protected boolean isSyntheticModule() { return false; } }