1 package org.coolreader.genrescollection; 2 3 import android.content.Context; 4 import android.content.res.Resources; 5 import android.content.res.XmlResourceParser; 6 import android.util.Log; 7 8 import org.xmlpull.v1.XmlPullParser; 9 import org.xmlpull.v1.XmlPullParserException; 10 11 import java.io.IOException; 12 import java.util.ArrayList; 13 import java.util.HashMap; 14 import java.util.LinkedHashMap; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Stack; 18 19 public class GenresCollection { 20 21 private static final String TAG = "genre"; 22 23 static public final class GenreRecord { 24 private final int m_id; 25 private final String m_code; 26 private String m_name; 27 private List<GenreRecord> m_childs; 28 private int m_level; 29 private List<String> m_aliases; 30 GenreRecord(int id, String code, String name, int level)31 private GenreRecord(int id, String code, String name, int level) { 32 m_id = id; 33 m_code = code; 34 m_name = name; 35 m_level = level; 36 m_childs = new ArrayList<>(); 37 m_aliases = new ArrayList<>(); 38 } 39 getId()40 public int getId() { 41 return m_id; 42 } 43 getCode()44 public String getCode() { 45 return m_code; 46 } 47 getName()48 public String getName() { 49 return m_name; 50 } 51 setName(String name)52 public void setName(String name) { 53 m_name = name; 54 } 55 getLevel()56 public int getLevel() { 57 return m_level; 58 } 59 getChilds()60 public List<GenreRecord> getChilds() { 61 return m_childs; 62 } 63 hasChilds()64 public boolean hasChilds() { 65 return null != m_childs && !m_childs.isEmpty(); 66 } 67 contain(String code)68 public boolean contain(String code) { 69 if (m_code.equals(code)) 70 return true; 71 for (GenreRecord record : m_childs) { 72 if (record.getCode().equals(code)) 73 return true; 74 } 75 return false; 76 } 77 hasAliases()78 public boolean hasAliases() { 79 return null != m_aliases && !m_aliases.isEmpty(); 80 } 81 getAliases()82 public List<String> getAliases() { 83 return m_aliases; 84 } 85 addAlias(String alias)86 private boolean addAlias(String alias) { 87 boolean exist = false; 88 for (String a : m_aliases) { 89 if (a.equals(alias)) { 90 exist = true; 91 break; 92 } 93 } 94 if (!exist) { 95 m_aliases.add(alias); 96 } 97 return !exist; 98 } 99 } 100 101 // main container 102 private Map<String, GenreRecord> m_collection; 103 // key: genre code 104 // value: genre record 105 private Map<String, GenreRecord> m_allGenres; 106 // instance 107 private static GenresCollection m_instance = null; 108 private int m_version; 109 GenresCollection()110 private GenresCollection() { 111 m_collection = new LinkedHashMap<>(); 112 m_allGenres = new HashMap<>(); 113 m_version = 0; 114 } 115 addGenre(int groupId, String groupCode, String groupName, int id, String code, String name)116 private void addGenre(int groupId, String groupCode, String groupName, int id, String code, String name) { 117 GenreRecord group = m_collection.get(groupCode); 118 if (null == group) { 119 group = new GenreRecord(groupId, groupCode, groupName, 0); 120 m_collection.put(groupCode, group); 121 m_allGenres.put(groupCode, group); 122 } 123 List<GenreRecord> groupChilds = group.getChilds(); 124 // Check if already exist in this group 125 GenreRecord exist = null; 126 for (GenreRecord rec : groupChilds) { 127 if (rec.getCode().equals(code)) { 128 exist = rec; 129 break; 130 } 131 } 132 if (null == exist) { 133 // add new child 134 GenreRecord record = m_allGenres.get(code); 135 if (null != record) { 136 if (id != record.getId()) { 137 Log.w(TAG, "genres: trying to add already existing genre '" + code + "' with different id: " + id + " != " + record.getId() + "!"); 138 Log.w(TAG, "genres: new id will be ignored."); 139 //} else { 140 // Log.d(TAG, "genres: for genre '" + code + "' duplicate found."); 141 } 142 } else { 143 record = new GenreRecord(id, code, name, 1); 144 m_allGenres.put(code, record); 145 } 146 groupChilds.add(record); 147 } else { 148 Log.w(TAG, "genres: genre with code '" + code + "' already exist in genre group '" + groupCode + "', skipped."); 149 } 150 } 151 addAliases(String genreCode, List<String> aliases)152 private void addAliases(String genreCode, List<String> aliases) { 153 GenreRecord genre = byCode(genreCode); 154 if (null != genre) { 155 for (String alias : aliases) { 156 if (genre.addAlias(alias)) 157 m_allGenres.put(alias, genre); 158 } 159 } else { 160 Log.w(TAG, "No such genre '" + genreCode + "' to register aliases!"); 161 } 162 } 163 getVersion()164 public int getVersion() { 165 return m_version; 166 } 167 byCode(String code)168 public GenreRecord byCode(String code) { 169 return m_allGenres.get(code); 170 } 171 byId(String strId)172 public GenreRecord byId(String strId) { 173 int id = -1; 174 try { 175 id = Integer.parseInt(strId, 10); 176 } catch (Exception ignored) { 177 } 178 if (id > 0) 179 return byId(id); 180 return null; 181 } 182 byId(int id)183 public GenreRecord byId(int id) { 184 GenreRecord genre = null; 185 for (Map.Entry<String, GenreRecord> entry : m_allGenres.entrySet()) { 186 if (entry.getValue().getId() == id) { 187 genre = entry.getValue(); 188 break; 189 } 190 } 191 return genre; 192 } 193 translate(String code)194 public String translate(String code) { 195 GenreRecord record = m_allGenres.get(code); 196 if (null != record) 197 return record.getName(); 198 // If not found, return as is. 199 return code; 200 } 201 getCollection()202 public Map<String, GenreRecord> getCollection() { 203 return m_collection; 204 } 205 getGroups()206 public List<String> getGroups() { 207 ArrayList<String> list = new ArrayList<>(); 208 for (Map.Entry<String, GenreRecord> entry : m_collection.entrySet()) { 209 list.add(entry.getKey()); 210 } 211 return list; 212 } 213 getInstance(Context context)214 public static GenresCollection getInstance(Context context) { 215 if (null == m_instance) { 216 m_instance = new GenresCollection(); 217 m_instance.loadGenresFromResource(context); 218 } 219 return m_instance; 220 } 221 reloadGenresFromResource(Context context)222 public static boolean reloadGenresFromResource(Context context) { 223 if (null == m_instance) { 224 m_instance = new GenresCollection(); 225 return m_instance.loadGenresFromResource(context); 226 } 227 return m_instance.loadGenresFromResource(context); 228 } 229 loadGenresFromResource(Context context)230 private boolean loadGenresFromResource(Context context) { 231 boolean res = false; 232 try { 233 XmlResourceParser parser = context.getResources().getXml(R.xml.union_genres); 234 // parser data 235 Stack<String> tagStack = new Stack<>(); 236 String tag; 237 String text = null; 238 String parentTag; 239 int groupId = -1; 240 String groupCode = null; 241 String groupName = null; 242 String str; 243 int genreId = -1; 244 String genreCode = null; 245 String genreName = null; 246 int count = 0; 247 // genre aliases 248 String aliasForCode = null; 249 String aliasCode; 250 // key: genre code 251 // value: list of aliases 252 HashMap<String, ArrayList<String>> allAliases = new HashMap<>(); 253 254 // start to parse 255 parser.next(); 256 int eventType = parser.getEventType(); 257 while (eventType != XmlPullParser.END_DOCUMENT) { 258 switch (eventType) { 259 case XmlPullParser.START_DOCUMENT: 260 //Log.d(TAG, "START_DOCUMENT"); 261 break; 262 case XmlPullParser.START_TAG: 263 tag = parser.getName(); 264 //Log.d(TAG, "START_TAG: " + tag); 265 if (!tagStack.empty()) 266 parentTag = tagStack.peek(); 267 else 268 parentTag = ""; 269 tagStack.push(tag); 270 if ("genres".equals(tag)) { 271 if (parentTag.length() == 0) { 272 // root tag found, clearing genre's collection 273 m_collection = new LinkedHashMap<>(); 274 m_version = -1; 275 str = parser.getAttributeValue(null, "version"); 276 if (null != str) { 277 try { 278 m_version = Integer.parseInt(str, 10); 279 } catch (Exception e) { 280 throw new XmlPullParserException(e.toString()); 281 } 282 } 283 if (m_version < 1) 284 throw new XmlPullParserException("Invalid resource version: " + str); 285 } else { 286 throw new XmlPullParserException("the element 'genres' must be the root element!"); 287 } 288 } else if ("group".equals(tag)) { 289 if ("genres".equals(parentTag)) { 290 groupCode = parser.getAttributeValue(null, "code"); 291 groupName = parser.getAttributeValue(null, "name"); 292 if (null != groupName) { 293 groupName = resolveStringResource(context, groupName); 294 } 295 groupId = -1; 296 str = parser.getAttributeValue(null, "id"); 297 if (null != str) { 298 try { 299 groupId = Integer.parseInt(str, 10); 300 } catch (Exception e) { 301 throw new XmlPullParserException(e.toString()); 302 } 303 } 304 if (groupId < 0) 305 throw new XmlPullParserException("Invalid group id: " + str); 306 } else { 307 throw new XmlPullParserException("the 'group' element must only be inside the 'genres' element!"); 308 } 309 } else if ("genre".equals(tag)) { 310 if ("group".equals(parentTag)) { 311 genreCode = parser.getAttributeValue(null, "code"); 312 genreName = parser.getAttributeValue(null, "name"); 313 if (null != genreName && genreName.length() > 0) { 314 genreName = resolveStringResource(context, genreName); 315 } 316 genreId = -1; 317 str = parser.getAttributeValue(null, "id"); 318 if (null != str) { 319 try { 320 genreId = Integer.parseInt(str, 10); 321 } catch (Exception e) { 322 throw new XmlPullParserException(e.toString()); 323 } 324 } 325 if (genreId < 0) 326 throw new XmlPullParserException("Invalid genre id: " + str); 327 } else { 328 throw new XmlPullParserException("the 'genre' element must only be inside the 'group' element!"); 329 } 330 } else if ("aliases".equals(tag)) { 331 if ("genres".equals(parentTag)) { 332 aliasForCode = null; 333 } else { 334 throw new XmlPullParserException("the 'aliases' element must only be inside the 'genres' element!"); 335 } 336 } else if ("alias".equals(tag)) { 337 if ("aliases".equals(parentTag)) { 338 aliasForCode = parser.getAttributeValue(null, "code"); 339 } else { 340 throw new XmlPullParserException("the 'alias' element must only be inside the 'aliases' element!"); 341 } 342 } 343 text = ""; 344 break; 345 case XmlPullParser.END_TAG: 346 //Log.d(TAG, "END_TAG: " + parser.getName()); 347 if (!tagStack.empty()) 348 tag = tagStack.pop(); 349 else 350 tag = ""; 351 if (!tag.equals(parser.getName())) { 352 throw new XmlPullParserException("end element '" + parser.getName() + "' not equal to start element '" + tag + "'"); 353 } 354 switch (tag) { 355 case "genre": 356 if (null != groupCode && groupCode.length() > 0 && 357 null != groupName && groupName.length() > 0 && 358 null != genreCode && genreCode.length() > 0 && 359 null != genreName && genreName.length() > 0) { 360 addGenre(groupId, groupCode, groupName, genreId, genreCode, genreName); 361 count++; 362 } 363 genreId = -1; 364 genreCode = null; 365 genreName = null; 366 break; 367 case "group": 368 groupId = -1; 369 groupCode = null; 370 groupName = null; 371 break; 372 case "alias": 373 // save alias to temporary container 374 aliasCode = text; 375 if (null != aliasForCode && aliasForCode.length() > 0 && 376 null != aliasCode && aliasCode.length() > 0) { 377 ArrayList<String> aliases = allAliases.get(aliasForCode); 378 if (null == aliases) { 379 aliases = new ArrayList<>(); 380 allAliases.put(aliasForCode, aliases); 381 } 382 // don't check already exist aliases here 383 // checked in GenreRecord.addAlias() 384 aliases.add(aliasCode); 385 } 386 aliasForCode = null; 387 break; 388 } 389 break; 390 case XmlPullParser.TEXT: 391 //Log.d(TAG, "TEXT: " + parser.getText()); 392 text = parser.getText(); 393 break; 394 } 395 eventType = parser.next(); 396 } 397 // Only after all genres are registered, add aliases 398 for (Map.Entry<String, ArrayList<String>> entry : allAliases.entrySet()) { 399 addAliases(entry.getKey(), entry.getValue()); 400 } 401 res = count > 0; 402 } catch (IndexOutOfBoundsException e) { 403 e.printStackTrace(); 404 } catch (IOException e) { 405 e.printStackTrace(); 406 } catch (XmlPullParserException e) { 407 e.printStackTrace(); 408 } catch (Resources.NotFoundException e) { 409 e.printStackTrace(); 410 } catch (NullPointerException e) { 411 e.printStackTrace(); 412 } 413 return res; 414 } 415 resolveStringResource(Context context, String resCode)416 private static String resolveStringResource(Context context, String resCode) { 417 if (null != resCode && resCode.startsWith("@")) { 418 try { 419 int resId = Integer.parseInt(resCode.substring(1), 10); 420 if (resId != 0) { 421 String str = context.getString(resId); 422 if (null != str && str.length() > 0) 423 return str; 424 } 425 } catch (NumberFormatException ignored) { 426 // ignore this exception, return original string 427 } 428 } 429 return resCode; 430 } 431 } 432