1
2/*
3 * Copyright (C) 2011 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package com.android.browser.homepages;
18
19import java.io.IOException;
20import java.io.InputStream;
21import java.io.OutputStream;
22import java.util.ArrayList;
23import java.util.HashMap;
24import java.util.List;
25import java.util.regex.Matcher;
26import java.util.regex.Pattern;
27
28import android.content.Context;
29import android.content.res.Resources;
30import android.database.Cursor;
31import android.util.TypedValue;
32
33import com.android.browser.R;
34
35public class Template {
36
37    private static HashMap<Integer, Template> sCachedTemplates = new HashMap<Integer, Template>();
38
39    public static Template getCachedTemplate(Context context, int id) {
40        synchronized (sCachedTemplates) {
41            Template template = sCachedTemplates.get(id);
42            if (template == null) {
43                template = new Template(context, id);
44                sCachedTemplates.put(id, template);
45            }
46            // Return a copy so that we don't share data
47            return template.copy();
48        }
49    }
50
51    interface Entity {
52        void write(OutputStream stream, EntityData params) throws IOException;
53    }
54
55    interface EntityData {
56        void writeValue(OutputStream stream, String key) throws IOException;
57        ListEntityIterator getListIterator(String key);
58    }
59
60    interface ListEntityIterator extends EntityData {
61        void reset();
62        boolean moveToNext();
63    }
64
65    static class StringEntity implements Entity {
66
67        byte[] mValue;
68
69        public StringEntity(String value) {
70            mValue = value.getBytes();
71        }
72
73        @Override
74        public void write(OutputStream stream, EntityData params) throws IOException {
75            stream.write(mValue);
76        }
77
78    }
79
80    static class SimpleEntity implements Entity {
81
82        String mKey;
83
84        public SimpleEntity(String key) {
85            mKey = key;
86        }
87
88        @Override
89        public void write(OutputStream stream, EntityData params) throws IOException {
90            params.writeValue(stream, mKey);
91        }
92
93    }
94
95    static class ListEntity implements Entity {
96
97        String mKey;
98        Template mSubTemplate;
99
100        public ListEntity(Context context, String key, String subTemplate) {
101            mKey = key;
102            mSubTemplate = new Template(context, subTemplate);
103        }
104
105        @Override
106        public void write(OutputStream stream, EntityData params) throws IOException {
107            ListEntityIterator iter = params.getListIterator(mKey);
108            iter.reset();
109            while (iter.moveToNext()) {
110                mSubTemplate.write(stream, iter);
111            }
112        }
113
114    }
115
116    public abstract static class CursorListEntityWrapper implements ListEntityIterator {
117
118        private Cursor mCursor;
119
120        public CursorListEntityWrapper(Cursor cursor) {
121            mCursor = cursor;
122        }
123
124        @Override
125        public boolean moveToNext() {
126            return mCursor.moveToNext();
127        }
128
129        @Override
130        public void reset() {
131            mCursor.moveToPosition(-1);
132        }
133
134        @Override
135        public ListEntityIterator getListIterator(String key) {
136            return null;
137        }
138
139        public Cursor getCursor() {
140            return mCursor;
141        }
142
143    }
144
145    static class HashMapEntityData implements EntityData {
146
147        HashMap<String, Object> mData;
148
149        public HashMapEntityData(HashMap<String, Object> map) {
150            mData = map;
151        }
152
153        @Override
154        public ListEntityIterator getListIterator(String key) {
155            return (ListEntityIterator) mData.get(key);
156        }
157
158        @Override
159        public void writeValue(OutputStream stream, String key) throws IOException {
160            stream.write((byte[]) mData.get(key));
161        }
162
163    }
164
165    private List<Entity> mTemplate;
166    private HashMap<String, Object> mData = new HashMap<String, Object>();
167    private Template(Context context, int tid) {
168        this(context, readRaw(context, tid));
169    }
170
171    private Template(Context context, String template) {
172        mTemplate = new ArrayList<Entity>();
173        template = replaceConsts(context, template);
174        parseTemplate(context, template);
175    }
176
177    private Template(Template copy) {
178        mTemplate = copy.mTemplate;
179    }
180
181    Template copy() {
182        return new Template(this);
183    }
184
185    void parseTemplate(Context context, String template) {
186        final Pattern pattern = Pattern.compile("<%([=\\{])\\s*(\\w+)\\s*%>");
187        Matcher m = pattern.matcher(template);
188        int start = 0;
189        while (m.find()) {
190            String static_part = template.substring(start, m.start());
191            if (static_part.length() > 0) {
192                mTemplate.add(new StringEntity(static_part));
193            }
194            String type = m.group(1);
195            String name = m.group(2);
196            if (type.equals("=")) {
197                mTemplate.add(new SimpleEntity(name));
198            } else if (type.equals("{")) {
199                Pattern p = Pattern.compile("<%\\}\\s*" + Pattern.quote(name) + "\\s*%>");
200                Matcher end_m = p.matcher(template);
201                if (end_m.find(m.end())) {
202                    start = m.end();
203                    m.region(end_m.end(), template.length());
204                    String subTemplate = template.substring(start, end_m.start());
205                    mTemplate.add(new ListEntity(context, name, subTemplate));
206                    start = end_m.end();
207                    continue;
208                }
209            }
210            start = m.end();
211        }
212        String static_part = template.substring(start, template.length());
213        if (static_part.length() > 0) {
214            mTemplate.add(new StringEntity(static_part));
215        }
216    }
217
218    public void assign(String name, String value) {
219        mData.put(name, value.getBytes());
220    }
221
222    public void assignLoop(String name, ListEntityIterator iter) {
223        mData.put(name, iter);
224    }
225
226    public void write(OutputStream stream) throws IOException {
227        write(stream, new HashMapEntityData(mData));
228    }
229
230    public void write(OutputStream stream, EntityData data) throws IOException {
231        for (Entity ent : mTemplate) {
232            ent.write(stream, data);
233        }
234    }
235
236    private static String replaceConsts(Context context, String template) {
237        final Pattern pattern = Pattern.compile("<%@\\s*(\\w+/\\w+)\\s*%>");
238        final Resources res = context.getResources();
239        final String packageName = R.class.getPackage().getName();
240        Matcher m = pattern.matcher(template);
241        StringBuffer sb = new StringBuffer();
242        while (m.find()) {
243            String name = m.group(1);
244            if (name.startsWith("drawable/")) {
245                m.appendReplacement(sb, "res/" + name);
246            } else {
247                int id = res.getIdentifier(name, null, packageName);
248                if (id != 0) {
249                    TypedValue value = new TypedValue();
250                    res.getValue(id, value, true);
251                    String replacement;
252                    if (value.type == TypedValue.TYPE_DIMENSION) {
253                        float dimen = res.getDimension(id);
254                        int dimeni = (int) dimen;
255                        if (dimeni == dimen)
256                            replacement = Integer.toString(dimeni);
257                        else
258                            replacement = Float.toString(dimen);
259                    } else {
260                        replacement = value.coerceToString().toString();
261                    }
262                    m.appendReplacement(sb, replacement);
263                }
264            }
265        }
266        m.appendTail(sb);
267        return sb.toString();
268    }
269
270    private static String readRaw(Context context, int id) {
271        InputStream ins = context.getResources().openRawResource(id);
272        try {
273            byte[] buf = new byte[ins.available()];
274            ins.read(buf);
275            return new String(buf, "utf-8");
276        } catch (IOException ex) {
277            return "<html><body>Error</body></html>";
278        }
279    }
280
281}
282