From ac88f79acc3ad931b5439ad46f2b2872ce590d42 Mon Sep 17 00:00:00 2001 From: REAndroid Date: Sat, 7 Jan 2023 09:32:06 -0500 Subject: [PATCH] fix issue: Styled strings merging. #5 --- .../java/com/reandroid/lib/apk/ApkBundle.java | 52 +++++- .../reandroid/lib/arsc/chunk/TableBlock.java | 2 +- .../reandroid/lib/arsc/item/StyleItem.java | 2 +- .../lib/arsc/pool/builder/SpannedText.java | 122 ------------- .../arsc/pool/builder/StringPoolMerger.java | 160 ++++++++++++++++++ 5 files changed, 213 insertions(+), 125 deletions(-) delete mode 100755 src/main/java/com/reandroid/lib/arsc/pool/builder/SpannedText.java create mode 100644 src/main/java/com/reandroid/lib/arsc/pool/builder/StringPoolMerger.java diff --git a/src/main/java/com/reandroid/lib/apk/ApkBundle.java b/src/main/java/com/reandroid/lib/apk/ApkBundle.java index da28805..90ecf9d 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkBundle.java +++ b/src/main/java/com/reandroid/lib/apk/ApkBundle.java @@ -17,6 +17,8 @@ package com.reandroid.lib.apk; import com.reandroid.archive.APKArchive; import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.arsc.pool.TableStringPool; +import com.reandroid.lib.arsc.pool.builder.StringPoolMerger; import java.io.File; import java.io.FileNotFoundException; @@ -35,8 +37,11 @@ public class ApkBundle { if(moduleList.size()==0){ throw new FileNotFoundException("Nothing to merge, empty modules"); } - ApkModule result=new ApkModule("merged", new APKArchive()); + ApkModule result=new ApkModule(generateMergedModuleName(), new APKArchive()); result.setAPKLogger(apkLogger); + + mergeStringPools(result); + ApkModule base=getBaseModule(); if(base==null){ base=getLargestTableModule(); @@ -56,6 +61,43 @@ public class ApkBundle { result.getApkArchive().sortApkFiles(); return result; } + private void mergeStringPools(ApkModule mergedModule) throws IOException { + if(!hasOneTableBlock() || mergedModule.hasTableBlock()){ + return; + } + logMessage("Merging string pools ... "); + TableBlock createdTable = new TableBlock(); + BlockInputSource inputSource= + new BlockInputSource<>(TableBlock.FILE_NAME, createdTable); + mergedModule.getApkArchive().add(inputSource); + + StringPoolMerger poolMerger = new StringPoolMerger(); + + for(ApkModule apkModule:getModules()){ + if(!apkModule.hasTableBlock()){ + continue; + } + TableStringPool stringPool = apkModule.getVolatileTableStringPool(); + poolMerger.add(stringPool); + } + + poolMerger.mergeTo(createdTable.getTableStringPool()); + + logMessage("Merged string pools="+poolMerger.getMergedPools() + +", style="+poolMerger.getMergedStyleStrings() + +", strings="+poolMerger.getMergedStrings()); + } + private String generateMergedModuleName(){ + Set moduleNames=mModulesMap.keySet(); + String merged="merged"; + int i=1; + String name=merged; + while (moduleNames.contains(name)){ + name=merged+"_"+i; + i++; + } + return name; + } private ApkModule getLargestTableModule() throws IOException { ApkModule apkModule=null; int chunkSize=0; @@ -123,6 +165,14 @@ public class ApkBundle { public Collection getModules(){ return mModulesMap.values(); } + private boolean hasOneTableBlock(){ + for(ApkModule apkModule:getModules()){ + if(apkModule.hasTableBlock()){ + return true; + } + } + return false; + } public void setAPKLogger(APKLogger logger) { this.apkLogger = logger; } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java index 756d481..9223a9d 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java @@ -180,7 +180,7 @@ public class TableBlock extends BaseChunk implements JSONConvert { if(tableBlock==null||tableBlock==this){ return; } - if(getPackageArray().childesCount()==0){ + if(getPackageArray().childesCount()==0 && getTableStringPool().countStrings()==0){ getTableStringPool().merge(tableBlock.getTableStringPool()); } getPackageArray().merge(tableBlock.getPackageArray()); diff --git a/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java b/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java index b3dbd50..55a3981 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java +++ b/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java @@ -160,7 +160,7 @@ public class StyleItem extends IntegerArray implements JSONConvert { } }; } - final List getSpanInfoList(){ + public final List getSpanInfoList(){ if(mSpanInfoList!=null){ return mSpanInfoList; } diff --git a/src/main/java/com/reandroid/lib/arsc/pool/builder/SpannedText.java b/src/main/java/com/reandroid/lib/arsc/pool/builder/SpannedText.java deleted file mode 100755 index c8e64d8..0000000 --- a/src/main/java/com/reandroid/lib/arsc/pool/builder/SpannedText.java +++ /dev/null @@ -1,122 +0,0 @@ - /* - * Copyright (C) 2022 github.com/REAndroid - * - * 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.reandroid.lib.arsc.pool.builder; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -public class SpannedText { - private String mTag; - private String mText; - private int mStart; - private int mEnd; - private List mChildes=new ArrayList<>(); - public SpannedText(){ - } - public void parse(String text){ - char[] allChars=text.toCharArray(); - int len=allChars.length; - String firstTag=null; - String endTag=null; - StringBuilder tag=null; - boolean firstTagFound=false; - int posFirst=0; - int posSecond=0; - for(int i=0;i'){ - if(!firstTagFound){ - firstTag=tag.toString(); - firstTagFound=true; - tag=null; - }else { - endTag=tag.toString(); - if(isTagsMatch(firstTag, endTag)){ - break; - } - endTag=null; - tag=null; - continue; - } - } - continue; - } - if(ch=='<'){ - if(isClosing(allChars, i+1)){ - if(!firstTagFound){ - tag=null; - continue; - } - }else if(firstTagFound){ - firstTagFound=false; - firstTag=null; - } - tag=new StringBuilder(); - tag.append(ch); - if(!firstTagFound){ - posFirst=i; - }else { - posSecond=i; - } - } - } - if(firstTag==null || endTag==null){ - return; - } - mStart=posFirst; - mEnd=posSecond; - StringBuilder builder=new StringBuilder(); - builder.append(text, 0, posFirst); - builder.append(text, posFirst+firstTag.length(), posSecond); - builder.append(text.substring(posSecond+endTag.length())); - mText=builder.toString(); - } - private boolean isClosing(char[] allChars, int pos){ - for(int i=pos;i0){ - start=start.substring(0,i); - } - start=start.trim(); - return start; - } - private String trimEndTag(String end){ - end=end.substring(1, end.length()-1).trim(); - end=end.substring(1).trim(); - return end; - } - private static final Pattern PATTERN_TAG=Pattern.compile("^(.*)(<[^/<>]+>)([^<]+)(]+>)(.*)$"); -} diff --git a/src/main/java/com/reandroid/lib/arsc/pool/builder/StringPoolMerger.java b/src/main/java/com/reandroid/lib/arsc/pool/builder/StringPoolMerger.java new file mode 100644 index 0000000..0467622 --- /dev/null +++ b/src/main/java/com/reandroid/lib/arsc/pool/builder/StringPoolMerger.java @@ -0,0 +1,160 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * 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.reandroid.lib.arsc.pool.builder; + +import com.reandroid.lib.arsc.array.StringArray; +import com.reandroid.lib.arsc.array.StyleArray; +import com.reandroid.lib.arsc.item.StyleItem; +import com.reandroid.lib.arsc.item.TableString; +import com.reandroid.lib.arsc.model.StyleSpanInfo; +import com.reandroid.lib.arsc.pool.TableStringPool; + +import java.util.*; + +public class StringPoolMerger implements Comparator { + private final Set mPools; + private int mMergedPools; + private int mMergedStrings; + private int mMergedStyleStrings; + public StringPoolMerger(){ + this.mPools=new HashSet<>(); + } + public void mergeTo(TableStringPool destination){ + mMergedPools=0; + mMergedStrings=0; + mMergedStyleStrings=0; + if(destination.countStrings()>0 || destination.countStyles()>0){ + throw new IllegalArgumentException("Destination string pool is not empty"); + } + mergeStyledStrings(destination); + mergeNonStyledStrings(destination); + mMergedPools = mPools.size(); + mPools.clear(); + destination.refresh(); + } + public void add(TableStringPool stringPool){ + mPools.add(stringPool); + } + public int getMergedPools() { + return mMergedPools; + } + public int getMergedStyleStrings() { + return mMergedStyleStrings; + } + public int getMergedStrings() { + return mMergedStrings; + } + + private void mergeStyledStrings(TableStringPool destination){ + List styledStrings = getStyledStrings(); + Map mapTableStrings = + destination.insertStrings(toStringList(styledStrings)); + Map mapTags = + destination.insertStrings(listStyleTags(styledStrings)); + + StyleArray styleArray = destination.getStyleArray(); + styleArray.setChildesCount(styledStrings.size()); + + for(TableString tableString:styledStrings){ + TableString createdString = mapTableStrings.get(tableString.get()); + StyleItem createdStyle = styleArray.get(createdString.getIndex()); + + StyleItem styleItem = tableString.getStyle(); + for(StyleSpanInfo spanInfo:styleItem.getSpanInfoList()){ + if(spanInfo!=null && createdStyle!=null){ + int tagReference = mapTags.get(spanInfo.getTag()) + .getIndex(); + createdStyle.addStylePiece( + tagReference, + spanInfo.getFirst(), + spanInfo.getLast()); + } + } + } + mMergedStyleStrings=styledStrings.size(); + } + private void mergeNonStyledStrings(TableStringPool destination){ + List nonStyledStrings=getNonStyledStrings(); + destination.insertStrings(nonStyledStrings); + mMergedStrings=nonStyledStrings.size(); + } + private List getStyledStrings(){ + Map mapUniqueHtml = new HashMap<>(); + for(TableStringPool pool:mPools){ + int styleCount = pool.countStyles(); + StringArray stringArray = pool.getStringsArray(); + for(int i=0;i(mapUniqueHtml.values()); + } + private List getNonStyledStrings(){ + Set uniqueSet = new HashSet<>(); + for(TableStringPool pool:mPools){ + TableString[] tableStrings = pool.getStrings(); + if(tableStrings==null){ + continue; + } + for(int i=0;i results=new ArrayList<>(uniqueSet); + results.sort(this); + return results; + } + private List toStringList(Collection tableStringList){ + List results=new ArrayList<>(tableStringList.size()); + for(TableString tableString:tableStringList){ + String str=tableString.get(); + if(str!=null){ + results.add(str); + } + } + results.sort(this); + return results; + } + private List listStyleTags(List styledStrings){ + Set resultSet=new HashSet<>(); + for(TableString tableString:styledStrings){ + StyleItem style = tableString.getStyle(); + if(style==null){ + continue; + } + for(StyleSpanInfo spanInfo:style.getSpanInfoList()){ + if(spanInfo!=null){ + resultSet.add(spanInfo.getTag()); + } + } + } + List results=new ArrayList<>(resultSet); + results.sort(this); + return results; + } + @Override + public int compare(String s1, String s2) { + return s1.compareTo(s2); + } +}