com.google.caja.precajole.StaticPrecajoleMap.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caja.precajole.StaticPrecajoleMap.java

Source

// Copyright (C) 2011 Google Inc.
//
// Licensed 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 com.google.caja.precajole;

import com.google.caja.SomethingWidgyHappenedError;
import com.google.caja.parser.js.CajoledModule;
import com.google.caja.util.Lists;
import com.google.caja.util.Maps;
import com.google.caja.util.Strings;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Hex;

/**
 * This is a PrecajoleMap that looks up precajoled modules from a
 * directory in the caja jar (or in the filesystem).
 * <p>
 * The directory contains serialized baked CajoledModules, stored
 * one per file.  Filenames are hex-encoded sha1 key of the uncajoled
 * source text.
 * <p>
 * There's also a file index.dat that contains a serialized map
 * of URI -> key.
 */

public class StaticPrecajoleMap implements PrecajoleMap {

    private static String SUBDIR_PATH = "com/google/caja/precajole/data/";
    private static String INDEX_NAME = "index.dat";

    public static StaticPrecajoleMap getInstance() {
        return InstanceHolder.instance;
    }

    private static class InstanceHolder {
        static StaticPrecajoleMap instance = new StaticPrecajoleMap("");
    }

    private static class Entry implements Serializable {
        private static final long serialVersionUID = 1L;
        final String[] uris;
        final String source;
        final CajoledModule minified;
        final CajoledModule pretty;
        final String id;

        Entry(String[] uris, String source, CajoledModule cajoled) {
            this.uris = uris;
            this.source = normalizeSource(source);
            this.minified = cajoled.flatten(true);
            this.pretty = cajoled.flatten(false);
            this.id = idForSource(this.source);
        }

        static Entry from(byte[] serial) {
            return (Entry) deserialize(serial);
        }
    }

    private static class Index implements Serializable {
        private static final long serialVersionUID = 1L;
        final public Map<String, String> map = Maps.newHashMap();
        public long modTime = 0L; // millisecond timestamp
    }

    //----

    private final String dir;
    private final Index index;

    public StaticPrecajoleMap(File baseDir) {
        this(baseDir.toString());
    }

    public StaticPrecajoleMap(String baseDir) {
        if (!baseDir.equals("") && !baseDir.endsWith("/")) {
            baseDir += "/";
        }
        this.dir = baseDir + SUBDIR_PATH;
        this.index = readIndex();
    }

    private Index readIndex() {
        Object o = deserialize(load(INDEX_NAME));
        if (o != null && o instanceof Index) {
            return (Index) o;
        } else {
            return new Index();
        }
    }

    public void put(List<String> uris, String source, CajoledModule cajoled) {
        put(uris.toArray(new String[uris.size()]), source, cajoled);
    }

    public void put(String[] uris, String source, CajoledModule cajoled) {
        Entry entry = new Entry(uris, source, cajoled);
        byte[] serial = serialize(entry);
        for (int k = 0; k < uris.length; k++) {
            index.map.put(normalizeUri(uris[k]), entry.id);
        }
        save(entry.id, serial);
    }

    public long getModTime() {
        return index.modTime;
    }

    public void setModTime(long millitime) {
        index.modTime = millitime;
    }

    public void finish() {
        save(INDEX_NAME, serialize(index));
    }

    @Override
    public CajoledModule lookupUri(String uri, boolean minify) {
        Entry e = Entry.from(load(idForUri(uri)));
        if (e != null && e.uris != null) {
            for (int k = 0; k < e.uris.length; k++) {
                if (uri.equals(e.uris[k])) {
                    return minify ? e.minified : e.pretty;
                }
            }
        }
        return null;
    }

    @Override
    public CajoledModule lookupSource(String source, boolean minify) {
        source = normalizeSource(source);
        Entry e = Entry.from(load(idForSource(source)));
        if (e != null && source.equals(e.source)) {
            return minify ? e.minified : e.pretty;
        }
        return null;
    }

    public List<List<String>> getUrlGroups() {
        Map<String, List<String>> idMap = Maps.newHashMap();
        for (String url : index.map.keySet()) {
            String id = index.map.get(url);
            List<String> urls = idMap.get(id);
            if (urls == null) {
                urls = Lists.newArrayList();
                idMap.put(id, urls);
            }
            urls.add(url);
        }
        List<List<String>> urlGroups = Lists.newArrayList();
        for (List<String> urls : idMap.values()) {
            urlGroups.add(urls);
        }
        return urlGroups;
    }

    public static String normalizeUri(String uri) {
        try {
            URI u = new URI(uri).normalize();
            if (u.getHost() != null) {
                u = new URI(lowercase(u.getScheme()), u.getUserInfo(), lowercase(u.getHost()), u.getPort(),
                        u.getPath(), u.getQuery(), u.getFragment());
            } else if (u.getScheme() != null) {
                u = new URI(lowercase(u.getScheme()), u.getSchemeSpecificPart(), u.getFragment());
            }
            return u.toString();
        } catch (URISyntaxException e) {
            return uri;
        }
    }

    private static String lowercase(String s) {
        return s == null ? null : Strings.lower(s);
    }

    private void save(String id, byte[] data) {
        try {
            new File(dir).mkdirs();
            FileOutputStream o = new FileOutputStream(new File(dir, id));
            o.write(data);
            o.close();
        } catch (IOException e) {
            throw new SomethingWidgyHappenedError(e);
        }
    }

    private String idForUri(String uri) {
        return index.map.get(normalizeUri(uri));
    }

    private static String idForSource(String source) {
        return computeHash(source);
    }

    private static String normalizeSource(String source) {
        // TODO(felix8a): I'd like to minify js here, but minifier is too slow
        return source.trim();
    }

    private byte[] load(String id) {
        byte[] result = loadResource(id);
        return result != null ? result : loadFile(id);
    }

    private byte[] loadFile(String id) {
        if (id == null) {
            return null;
        }
        try {
            return Files.toByteArray(new File(dir, id));
        } catch (IOException e) {
            return null;
        }
    }

    private byte[] loadResource(String id) {
        if (id == null) {
            return null;
        }
        ClassLoader cl = StaticPrecajoleMap.class.getClassLoader();
        InputStream is = cl.getResourceAsStream(dir + id);
        if (is == null) {
            return null;
        }
        try {
            return ByteStreams.toByteArray(is);
        } catch (IOException e) {
            return null;
        }
    }

    private static String computeHash(String s) {
        try {
            MessageDigest sha1 = MessageDigest.getInstance("SHA1");
            byte[] digest = sha1.digest(s.getBytes("UTF-8"));
            return new String(Hex.encodeHex(digest));
        } catch (NoSuchAlgorithmException e) {
            throw new SomethingWidgyHappenedError(e);
        } catch (UnsupportedEncodingException e) {
            throw new SomethingWidgyHappenedError(e);
        }
    }

    // TODO(felix8a): protobuf is much faster
    private static byte[] serialize(Object obj) {
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        try {
            ObjectOutputStream ostr = new ObjectOutputStream(buf);
            ostr.writeObject(obj);
            ostr.close();
        } catch (IOException e) {
            throw new SomethingWidgyHappenedError(e);
        }
        return buf.toByteArray();
    }

    private static Object deserialize(byte[] serial) {
        if (serial == null) {
            return null;
        }
        try {
            ByteArrayInputStream b = new ByteArrayInputStream(serial);
            ObjectInputStream i = new ObjectInputStream(b);
            return i.readObject();
        } catch (IOException e) {
            return null;
        } catch (ClassNotFoundException e) {
            return null;
        }
    }
}