Java tutorial
/******************************************************************************* * Copyright (c) 2014 itemis AG (http://www.itemis.eu) 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 *******************************************************************************/ package org.eclipse.xtext.testing.formatter; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Collection; import java.util.List; import java.util.Set; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.formatting2.FormatterRequest; import org.eclipse.xtext.formatting2.IFormatter2; import org.eclipse.xtext.formatting2.debug.TextRegionAccessToString; import org.eclipse.xtext.formatting2.debug.TextRegionsToString; import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion; import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement; import org.eclipse.xtext.formatting2.regionaccess.ITextSegment; import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder; import org.eclipse.xtext.formatting2.regionaccess.internal.TextRegions; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.SyntaxErrorMessage; import org.eclipse.xtext.preferences.MapBasedPreferenceValues; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.serializer.impl.Serializer; import org.eclipse.xtext.testing.util.ParseHelper; import org.eclipse.xtext.util.ExceptionAcceptor; import org.eclipse.xtext.util.ITextRegion; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; import org.junit.Assert; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Provider; /** * @author Moritz Eysholdt - Initial contribution and API */ public class FormatterTestHelper { @Inject(optional = true) private Provider<IFormatter2> formatter; @Inject private Provider<FormatterTestRequest> formatterRequestProvider; @Inject private Provider<TextRegionAccessBuilder> textRegionBuilderProvider; @Inject private ParseHelper<EObject> parseHelper; @Inject private Serializer serializer; protected void assertAllWhitespaceIsFormatted(ITextRegionAccess access, List<ITextReplacement> replacements) { List<ITextSegment> expected = Lists.newArrayList(); IHiddenRegion current = access.regionForRootEObject().getPreviousHiddenRegion(); while (current != null) { expected.addAll(current.getMergedSpaces()); current = current.getNextHiddenRegion(); } List<ITextSegment> missing = TextRegions.difference(expected, replacements); if (!missing.isEmpty()) { TextRegionsToString toString = new TextRegionsToString().setTextRegionAccess(access); for (ITextSegment region : missing) toString.add(region, region.getClass().getSimpleName()); String msg = "The following regions are not formatted:\n" + toString; System.err.println(msg); Assert.fail(msg); } } public void assertFormatted(FormatterTestRequest req) { checkNotNull(req); checkNotNull(req.getToBeFormatted()); FormatterRequest request = req.getRequest(); checkArgument(request.getTextRegionAccess() == null); String document = req.getToBeFormatted().toString(); XtextResource parsed = parse(document); if (req.isAllowSyntaxErrors()) { if (request.getExplicitExceptionHandler() == null) { request.setExceptionHandler(ExceptionAcceptor.IGNORING); } } else { assertNoSyntaxErrors(parsed); if (request.getExplicitExceptionHandler() == null) { request.setExceptionHandler(ExceptionAcceptor.THROWING); } } request.setTextRegionAccess(createRegionAccess(parsed, req)); if (request.getPreferences() == null) request.setPreferences(new MapBasedPreferenceValues(Maps.<String, String>newLinkedHashMap())); List<ITextReplacement> replacements = createFormatter(req).format(request); assertReplacementsAreInRegion(replacements, request.getRegions(), document); if (!req.isAllowUnformattedWhitespace()) assertAllWhitespaceIsFormatted(request.getTextRegionAccess(), replacements); String formatted = request.getTextRegionAccess().getRewriter().renderToString(replacements); Assert.assertEquals(req.getExpectationOrToBeFormatted().toString(), formatted); // TODO: assert formatting a second time only produces identity replacements // TODO: assert formatting with undefined whitespace only } public void assertFormatted(Procedure1<FormatterTestRequest> init) { FormatterTestRequest request = formatterRequestProvider.get(); init.apply(request); assertFormatted(request); } protected void assertNoSyntaxErrors(XtextResource resource) { Iterable<INode> syntaxErrors = resource.getParseResult().getSyntaxErrors(); if (!Iterables.isEmpty(syntaxErrors)) { StringBuilder builder = new StringBuilder(); builder.append("This document can't be formatted because of syntax errors:\n"); for (INode node : syntaxErrors) { SyntaxErrorMessage msg = node.getSyntaxErrorMessage(); builder.append(String.format("Line %02d: %s\n", node.getTotalStartLine(), msg.getMessage())); } fail(builder, resource.getParseResult().getRootNode().getText()); } } protected void assertReplacementsAreInRegion(List<ITextReplacement> rep, Collection<ITextRegion> regions, String doc) { Set<ITextReplacement> invalid = Sets.newHashSet(); ALLOWED: for (ITextRegion allowed : regions) for (ITextReplacement r : rep) { if (allowed.contains(r)) continue ALLOWED; invalid.add(r); } if (!invalid.isEmpty()) { String visualized = new TextRegionsToString().addAllReplacements(invalid).toString(); fail("One or more TextReplacements are outside of the allowed region. Region: " + regions, visualized); } } protected IFormatter2 createFormatter(FormatterTestRequest request) { checkNotNull(formatter, "There is a Guice Binding missing for " + IFormatter2.class.getName()); return formatter.get(); } protected ITextRegionAccess createRegionAccess(XtextResource resource, FormatterTestRequest req) { boolean useSerializer = req.isUseSerializer() && !req.isAllowSyntaxErrors(); if (req.isUseNodeModel() && useSerializer) { ITextRegionAccess nmRegions = createRegionAccessViaNodeModel(resource); ITextRegionAccess serRegions = createRegionAccessViaSerializer(resource); String nmString = toString(nmRegions); String serString = toString(serRegions); Assert.assertEquals(nmString, serString); return nmRegions; } else if (req.isUseNodeModel()) { ITextRegionAccess nmRegions = createRegionAccessViaNodeModel(resource); return nmRegions; } else if (useSerializer) { ITextRegionAccess serRegions = createRegionAccessViaSerializer(resource); return serRegions; } else throw new IllegalStateException("Can't format anything when using neither NodeModel nor Serializer."); } protected ITextRegionAccess createRegionAccessViaNodeModel(XtextResource resource) { ITextRegionAccess access = textRegionBuilderProvider.get().forNodeModel(resource).create(); return access; } protected ITextRegionAccess createRegionAccessViaSerializer(XtextResource resource) { EObject root = resource.getContents().get(0); ITextRegionAccess regionAccess = serializer.serializeToRegions(root); return regionAccess; } protected void fail(CharSequence error, CharSequence document) { StringBuilder builder = new StringBuilder(Strings.trimTrailingLineBreak(error)); if (document != null) { builder.append("\n----------------------------------------------------------\n"); builder.append(Strings.trimTrailingLineBreak(document)); builder.append("\n----------------------------------------------------------"); } String msg = builder.toString(); System.err.println(msg); Assert.fail(msg); } protected XtextResource parse(String document) { try { return (XtextResource) parseHelper.parse(document).eResource(); } catch (Exception e) { throw new RuntimeException(e); } } protected String toString(ITextRegionAccess nmRegions) { return new TextRegionAccessToString().withRegionAccess(nmRegions).hideColumnExplanation() + "\n"; } }