/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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
*
* 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 com.gwtplugins.gwt.eclipse.gss.model;
import com.google.gwt.eclipse.core.GWTPluginLog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.wst.css.core.internal.formatter.CSSSourceFormatter;
import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.css.core.internal.util.AbstractCssTraverser;
import org.eclipse.wst.sse.core.internal.parser.ContextRegion;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.w3c.dom.css.CSSMediaRule;
import org.w3c.dom.stylesheets.MediaList;
import java.lang.reflect.Method;
/**
* Repairs the CSS model of CSS Resource custom at-rules. The
* {@link GssResourceAwareTokenizer} returns "@media" for these custom at-rules
* which allows the WST CSS implementation deal with most of them, but there is
* still some additional repairing that must be done:
* <ul>
* <li>The "@else" and "@noflip" rules do not have any text following them (e.g.
* "@if _____ {" vs. "@else {"), but the media rule parser expects at least one.
* Since the media rule parser does not find one, it creates a media rule model
* object that isn't properly initialized with a valid range. This class repairs
* that range to allow the WST CSS implementation to deal with these rules.</li>
* </ul>
*/
@SuppressWarnings("restriction")
public class GssResourceAwareModelRepairer {
private class MyCssTraverser extends AbstractCssTraverser {
@Override
protected short preNode(ICSSNode node) {
if (node.getNodeType() == ICSSNode.MEDIARULE_NODE) {
fixPotentialEmptyMaskedMediaRule(node);
}
return TRAV_CONT;
}
}
/**
* If the media list of any of these rules is empty, the repairer will add a
* dummy item to the list.
*/
private static final String[] EMPTY_MASKED_MEDIA_RULES = {
"@if", "@elif", "@else", "@noflip"};
/**
* Calls the {@link MediaList} <code>setRangeRegion</code> method.
*
* @param mediaList the MediaList object that receives the call
* @param structuredDocumentRegions the first parameter of the method
* @param textRegion the second parameter of the method
* @throws Throwable for safeguarding against any reflection issues (nothing
* is logged in this method since it doesn't have proper context of
* the scenario)
*/
private static void callSetRangeRegion(MediaList mediaList,
IStructuredDocumentRegion[] structuredDocumentRegions,
ITextRegion textRegion) throws Throwable {
ClassLoader classLoader = CSSSourceFormatter.class.getClassLoader();
Class<?> cssRegionContainerClass = classLoader.loadClass("org.eclipse.wst.css.core.internal.document.CSSRegionContainer");
Method declaredMethod = cssRegionContainerClass.getDeclaredMethod(
"setRangeRegion", IStructuredDocumentRegion.class, ITextRegion.class,
ITextRegion.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke(mediaList, structuredDocumentRegions[0], textRegion,
textRegion);
}
private final IStructuredDocument structuredDocument;
private final ICSSModel cssModel;
public GssResourceAwareModelRepairer(IStructuredDocument structuredDocument,
ICSSModel cssModel) {
this.structuredDocument = structuredDocument;
this.cssModel = cssModel;
}
public void repair() {
new MyCssTraverser().apply(cssModel);
}
private boolean containsEmptyMaskedMediaRule(CSSMediaRule mediaRule,
IndexedRegion mediaRuleRegion) {
for (String rule : EMPTY_MASKED_MEDIA_RULES) {
try {
if (structuredDocument.getLength() < rule.length()) {
continue;
}
if (!rule.equalsIgnoreCase(structuredDocument.get(
mediaRuleRegion.getStartOffset(), rule.length()))) {
continue;
}
if (mediaRule.getMedia().getLength() > 0) {
continue;
}
return true;
} catch (BadLocationException e1) {
// Shouldn't happen, continue on
}
}
return false;
}
private void fixPotentialEmptyMaskedMediaRule(ICSSNode node) {
CSSMediaRule mediaRule = (CSSMediaRule) node;
IndexedRegion mediaRuleRegion = (IndexedRegion) mediaRule;
if (!containsEmptyMaskedMediaRule(mediaRule, mediaRuleRegion)) {
return;
}
// Set the range to a valid value (it won't be proper since we don't have
// any additional words that can be categorized as CSS_MEDIUM.)
MediaList mediaList = mediaRule.getMedia();
IStructuredDocumentRegion[] structuredDocumentRegions = structuredDocument.getStructuredDocumentRegions(
mediaRuleRegion.getStartOffset(), mediaRuleRegion.getLength());
// The value we set is a 0-length region starting where the next word would
// have been
ITextRegion textRegion = new ContextRegion(CSSRegionContexts.CSS_MEDIUM,
structuredDocumentRegions[0].getEndOffset()
- structuredDocumentRegions[0].getStartOffset(), 0, 0);
try {
callSetRangeRegion(mediaList, structuredDocumentRegions, textRegion);
} catch (Throwable e) {
GWTPluginLog.logError(e, "Could not clean up the @else in the CSS model.");
}
}
}