All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.royale.compiler.internal.resourcebundles.PropertiesFileParser Maven / Gradle / Ivy

There is a newer version: 0.9.12
Show newest version
/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  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 org.apache.royale.compiler.internal.resourcebundles;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.common.SourceLocation;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.internal.parsing.as.ASParser;
import org.apache.royale.compiler.internal.tree.as.ClassReferenceNode;
import org.apache.royale.compiler.internal.tree.as.EmbedNode;
import org.apache.royale.compiler.internal.tree.as.ExpressionNodeBase;
import org.apache.royale.compiler.internal.tree.as.LiteralNode;
import org.apache.royale.compiler.internal.tree.as.metadata.MetaTagsNode;
import org.apache.royale.compiler.internal.tree.properties.ResourceBundleEntryNode;
import org.apache.royale.compiler.internal.tree.properties.ResourceBundleFileNode;
import org.apache.royale.compiler.problems.FileNotFoundProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.InternalCompilerProblem2;
import org.apache.royale.compiler.problems.ParserProblem;
import org.apache.royale.compiler.problems.ResourceBundleMalformedEncodingProblem;
import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
import org.apache.royale.compiler.tree.metadata.IMetaTagNode;
import org.apache.royale.compiler.workspaces.IWorkspace;

/**
 * Properties parser that reads a properties file in Unicode. 
 */
public class PropertiesFileParser
{
    public static final Pattern CLASS_REFERENCE_REGEX = Pattern.compile("ClassReference\\((.*)\\)");
    public static final Pattern EMBED_REGEX = Pattern.compile(IMetaAttributeConstants.ATTRIBUTE_EMBED + "\\(.*\\)");
    
    /**
     * Characters we skip in certain places
     */
    private static final String WHITESPACE = " \t\n\r\f";

    /**
     * Characters that terminate a key
     */
    private static String SPLITTERS = "=: \t";

    /**
     * Characters that terminate a value
     */
    private static final String TERMINATORS = "\n\r\f";
    
    /**
     * Path of the file that is parsed.
     */
    private String filePath;
    
    /**
     * File node for the properties file being parsed
     */
    private ResourceBundleFileNode fileNode;
    
    /**
     * Collection that is used to store problems that occur during parsing.
     */
    private Collection problems;
    
    private IWorkspace workspace;

    /**
     * Constructor
     */
    public PropertiesFileParser(IWorkspace workspace) {
        this.workspace = workspace;
    }

    /**
     * This method attempts to parse a .properties file
     * using the same rules as Java, except that the file
     * is assumed to have UTF-8 encoding.
     * 
     * Let  indicates optional whitespace and  required whitespace.
     * 
     * Comment lines have the form # or !
     * If # or ! isn't the first non-whitespace character on a line,
     * it doesn't start a comment.
     * 
     * Key/value pairs have the form key=value
     * or key:value or keyvalue
     * In other words, you can use an equal sign, a colon,
     * or just whitespace to separate the key from the value.
     * 
     * Trailing whitespace is not stripped from the value.
     * 
     * You can use standard escape sequences
     * like \n, \r, \t, \u1234, and \\.
     * 
     * Backslash-space is an escape sequence for a space;
     * for example, if a value needs to start with a space
     * you must write it as backslash-space or it will be
     * interpreted as optional whitespace preceding the value.
     * However, you don't need to escape spaces within a value.
     *   
     * You can continue a line by ending it with a backslash.
     * Leading whitespace on the next line is stripped.
     *      
     * Backslashes that aren't part of an escape sequence are removed.
     * For example, \A is just A.
     *      
     * You don't need to escape a double-quote or a single-quote
     * (but it doesn't hurt to do so).
     * 
     * @param filePath path of the properties file to parse.
     * @param locale locale of the file if it is locale dependent, otherweise null
     * @param reader reader that wraps an open stream to the file to parse.
     * @param problems collection that is used to store problems that occur
     * during parsing
     */
    public ResourceBundleFileNode parse(String filePath, String locale, 
            Reader reader, Collection problems) {
        this.filePath = filePath;
        this.problems = problems;

        try
        {
            fileNode = new ResourceBundleFileNode(workspace, filePath, locale);
            
            parse(new BufferedReader(reader));
                        
            return fileNode;
        }
        catch(FileNotFoundException ex) 
        {
            ICompilerProblem problem = new FileNotFoundProblem(filePath);
            problems.add(problem);
        }
        catch (IOException ex)
        {
            ICompilerProblem problem = new InternalCompilerProblem2(filePath, ex, "PropertiesFileParser");
            problems.add(problem);
        }
        finally
        {
            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (IOException ex)
                {
                }
            }
        }
        
        return null;
    }

    /**
     * Parses the properties file.
     * 
     * @param br buffered reader to read the properties file.
     * @throws IOException
     */
    private void parse(BufferedReader br) throws IOException
    {
        String line;
        StringBuilder buffer = new StringBuilder(100);
        int lineNumber = 0;
        int comment_length=0;
        String sep = System.getProperty("line.separator");
        int sep_len = sep.length();
        int offset = 0;

        while((line=br.readLine())!=null) {
            lineNumber++;

            int len = line.length();
            offset+=len;
            int start=0;

            // TODO: Clean this up some day by using: 
            // http://commons.apache.org/io/api-release/org/apache/commons/io/input/BOMInputStream.html
            // skip the Unicode BOM; UTF-8 is indicated by the byte sequence
            // EF BB BF, which is the UTF-8 encoding of the character U+FEFF)
            if (lineNumber == 1 && len > 0 && line.charAt(0) == '\uFEFF') {
                line = line.substring(1);
                len = line.length();
                offset = len;
            }

            // find first non-whitespace char
            for(;start 1 && line.charAt(line.length()-1)=='\\') {
                buffer.setLength(buffer.length()-1); // remove the backslash
                line=br.readLine();
                if(line!=null) {
                    int new_start = 0;
                    len = line.length();
                    // find first non-whitespace char

                    for(;new_start < len &&
                    WHITESPACE.indexOf(line.charAt(new_start))!=-1;
                    new_start++);

                    // add to buffer
                    buffer.append(line.substring(new_start));
                }
            }

            String propLine = buffer.toString();
            String com_key = loadProperty(propLine, lineNumber, offset, start);

            if(comment_length!=0 && com_key != null) {
                comment_length=0;
            }

            buffer.setLength(0);
        }
    }

    /**
     * Parses a line in a property file.
     * 
     * @param prop
     * @param lineNumber
     * @return
     */
    private String loadProperty(String property, int lineNumber, int startOffset, int column)
    {
        String key;
        String value;
        int prop_len=property.length();
        int prop_index=0;

        // key
        for(; prop_index problems)
    {
        LiteralNode keyNode = new LiteralNode(LiteralType.STRING, key, keySource);
        
        ExpressionNodeBase valueNode = null; 
        Matcher matcher;
        if ((matcher = CLASS_REFERENCE_REGEX.matcher(value)).matches())
        {
            valueNode = processClassReference(matcher, valueSource, problems);
        }
        else if ((matcher = EMBED_REGEX.matcher(value)).matches())
        {
            valueNode = processEmbed(value, valueSource, problems);
        }
        else
        {
            valueNode = new LiteralNode(LiteralType.STRING, value, valueSource);
        }
        
        if(valueNode != null)
            fileNode.addItem(new ResourceBundleEntryNode(keyNode, valueNode));  
    }

    /**
     * Process a ClassReference directive that occurs in a properties file.
     * 
     * @param matcher matcher that has already identified a ClassReference directive in a value.
     * @param sourceLocation location where this directive occurred in the file
     * @param problems collection to add problems if encountered during
     * processing.
     * @return a {@link ClassReferenceNode} instance that represents this
     * occurrence or null if any problem occurs.
     */
    private ClassReferenceNode processClassReference(Matcher matcher, SourceLocation sourceLocation,
            Collection problems)
    {        
        try
        {
            String qName = matcher.group(1).trim();
            if (qName.equals("null"))
            {
                return new ClassReferenceNode(null, sourceLocation);
            }

            if ((qName.charAt(0) == '"') && (qName.indexOf('"', 1) == qName.length() - 1))
            {
                qName = qName.substring(1, qName.length() - 1);
                return new ClassReferenceNode(qName, sourceLocation);
            }
        } 
        catch(Exception e)
        {
            //do nothing, problem will be reported next.
        }
               
        ParserProblem problem = new ParserProblem(sourceLocation);
        problems.add(problem);
            
        return null;
    }
    
    /**
     * Process a Embed directive that occurs in a properties file.
     * 
     * @param value Embed directive to process
     * @param sourceLocation location where this directive occurred in the file
     * @param problems collection to add problems if encountered during
     * processing.
     * @return a {@link EmbedNode} instance that represents this
     * occurrence or null if any problem occurs.
     */
    private EmbedNode processEmbed(String value, SourceLocation sourceLocation,
                                   Collection problems)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        sb.append(value);
        sb.append("]");
        
        MetaTagsNode metaTagsNode =  ASParser.parseMetadata(
            workspace, sb.toString(), sourceLocation.getSourcePath(), sourceLocation.getAbsoluteStart(), 
            sourceLocation.getLine(), sourceLocation.getColumn(), problems);
        
        if (metaTagsNode == null)
            return null;

        IMetaTagNode embedMetaTagNode = metaTagsNode.getTagByName(IMetaAttributeConstants.ATTRIBUTE_EMBED);
        if (embedMetaTagNode == null)
            return null;
        
        EmbedNode embedNode = new EmbedNode(filePath, embedMetaTagNode, fileNode);
        embedNode.setSourceLocation(sourceLocation);
        
        return embedNode;
    }
 
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy