Changeset 4816 in josm for trunk/src/org/openstreetmap/josm


Ignore:
Timestamp:
18.01.2012 19:48:23 (4 months ago)
Author:
simon04
Message:

fix #7257, fix #4093 - Provide an option to automatically download elements after a reference error

Location:
trunk/src/org/openstreetmap/josm
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/HelpAwareOptionPane.java

    r3501 r4816  
    1313import java.util.List; 
    1414 
    15 import javax.swing.AbstractAction; 
    16 import javax.swing.Action; 
    17 import javax.swing.Icon; 
    18 import javax.swing.JButton; 
    19 import javax.swing.JComponent; 
    20 import javax.swing.JDialog; 
    21 import javax.swing.JLabel; 
    22 import javax.swing.JOptionPane; 
    23 import javax.swing.KeyStroke; 
    24 import javax.swing.SwingUtilities; 
     15import javax.swing.*; 
    2516 
    2617import org.openstreetmap.josm.gui.help.HelpBrowser; 
     
    167158 
    168159        if (msg instanceof String) { 
    169             msg = new JLabel((String)msg); 
     160            JEditorPane pane = new JEditorPane(); 
     161            pane.setContentType("text/html"); 
     162            pane.setText((String) msg); 
     163            pane.setEditable(false); 
     164            pane.setOpaque(false); 
     165            msg = pane; 
    170166        } 
    171167 
  • trunk/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java

    r4191 r4816  
    88import java.net.HttpURLConnection; 
    99import java.text.SimpleDateFormat; 
     10import java.util.Arrays; 
     11import java.util.Collection; 
    1012import java.util.Collections; 
    1113import java.util.Date; 
     
    3133import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException; 
    3234import org.openstreetmap.josm.tools.DateUtils; 
     35import org.openstreetmap.josm.tools.ExceptionUtil; 
    3336import org.openstreetmap.josm.tools.ImageProvider; 
     37import org.openstreetmap.josm.tools.Pair; 
    3438 
    3539public abstract class AbstractUploadTask extends PleaseWaitRunnable { 
     
    206210     * a non-deleted way on the server. 
    207211     */ 
    208     protected void handleUploadConflictForNodeStillInUse(long nodeId, long wayId) { 
     212    protected void handleUploadPreconditionFailedConflict(OsmApiException e, Pair<OsmPrimitive, Collection<OsmPrimitive>> conflict) { 
    209213        ButtonSpec[] options = new ButtonSpec[] { 
    210214                new ButtonSpec( 
    211215                        tr("Prepare conflict resolution"), 
    212216                        ImageProvider.get("ok"), 
    213                         tr("Click to download all parent ways for node {0}", nodeId), 
     217                        tr("Click to download all referring objects for {0}", conflict.a), 
    214218                        null /* no specific help context */ 
    215219                ), 
     
    217221                        tr("Cancel"), 
    218222                        ImageProvider.get("cancel"), 
    219                         tr("Click to cancel and to resume editing the map", nodeId), 
     223                        tr("Click to cancel and to resume editing the map"), 
    220224                        null /* no specific help context */ 
    221225                ) 
    222226        }; 
    223         String msg =  tr("<html>Uploading <strong>failed</strong> because you tried " 
    224                 + "to delete node {0} which is still in use in way {1}.<br><br>" 
    225                 + "Click <strong>{2}</strong> to download all parent ways of node {0}.<br>" 
    226                 + "If necessary JOSM will create conflicts which you can resolve in the Conflict Resolution Dialog." 
    227                 + "</html>", 
    228                 nodeId, wayId, options[0].text 
    229         ); 
    230  
     227        String msg = ExceptionUtil.explainPreconditionFailed(e).replace("</html>", "<br><br>" + tr( 
     228                "Click <strong>{0}</strong> to load them now.<br>" 
     229                + "If necessary JOSM will create conflicts which you can resolve in the Conflict Resolution Dialog.", 
     230                options[0].text)) + "</html>"; 
    231231        int ret = HelpAwareOptionPane.showOptionDialog( 
    232232                Main.parent, 
    233233                msg, 
    234                 tr("Node still in use"), 
     234                tr("Object still in use"), 
    235235                JOptionPane.ERROR_MESSAGE, 
    236236                null, 
     
    238238                options[0], 
    239239                "/Action/Upload#NodeStillInUseInWay" 
    240         ); 
    241         if (ret != 0) return; 
    242         DownloadReferrersAction.downloadReferrers(Main.map.mapView.getEditLayer(), nodeId, OsmPrimitiveType.NODE); 
     240); 
     241        if (ret == 0) { 
     242            DownloadReferrersAction.downloadReferrers(Main.map.mapView.getEditLayer(), Arrays.asList(conflict.a)); 
     243        } 
    243244    } 
    244245 
     
    263264            return; 
    264265        } 
    265         pattern = "Node (\\d+) is still used by way (\\d+)."; 
    266         p = Pattern.compile(pattern); 
    267         m = p.matcher(e.getErrorHeader()); 
    268         if (m.matches()) { 
    269             handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2))); 
    270             return; 
    271         } 
    272266        System.out.println(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader())); 
    273267        handleUploadConflictForUnknownConflict(); 
     
    280274     */ 
    281275    protected void handlePreconditionFailed(OsmApiException e) { 
    282         String pattern = "Precondition failed: Node (\\d+) is still used by way (\\d+)."; 
    283         Pattern p = Pattern.compile(pattern); 
    284         Matcher m = p.matcher(e.getErrorHeader()); 
    285         if (m.matches()) { 
    286             handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2))); 
    287             return; 
    288         } 
    289         System.out.println(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader())); 
    290         ExceptionDialogUtil.explainPreconditionFailed(e); 
     276        // in the worst case, ExceptionUtil.parsePreconditionFailed is executed trice - should not be too expensive 
     277        Pair<OsmPrimitive, Collection<OsmPrimitive>> conflict = ExceptionUtil.parsePreconditionFailed(e.getErrorHeader()); 
     278        if (conflict != null) { 
     279            handleUploadPreconditionFailedConflict(e, conflict); 
     280        } else { 
     281            System.out.println(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader())); 
     282            ExceptionDialogUtil.explainPreconditionFailed(e); 
     283        } 
    291284    } 
    292285 
  • trunk/src/org/openstreetmap/josm/tools/ExceptionUtil.java

    r4703 r4816  
    2323 
    2424import org.openstreetmap.josm.Main; 
     25import org.openstreetmap.josm.data.osm.Node; 
     26import org.openstreetmap.josm.data.osm.OsmPrimitive; 
     27import org.openstreetmap.josm.data.osm.Relation; 
     28import org.openstreetmap.josm.data.osm.Way; 
    2529import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; 
    2630import org.openstreetmap.josm.io.ChangesetClosedException; 
     
    3337import org.openstreetmap.josm.io.auth.CredentialsManager; 
    3438 
     39@SuppressWarnings("CallToThreadDumpStack") 
    3540public class ExceptionUtil { 
    3641    private ExceptionUtil() { 
     
    4651        String msg = tr( 
    4752                "<html>Failed to initialize communication with the OSM server {0}.<br>" 
    48                 + "Check the server URL in your preferences and your internet connection.</html>", Main.pref.get( 
     53                + "Check the server URL in your preferences and your internet connection.", Main.pref.get( 
    4954                        "osm-server.url", "http://api.openstreetmap.org/api")); 
    5055        return msg; 
     
    7075    } 
    7176 
    72     /** 
    73      * Explains a precondition exception when a child relation could not be deleted because 
    74      * it is still referred to by an undeleted parent relation. 
    75      * 
    76      * @param e the exception 
    77      * @param childRelation the child relation 
    78      * @param parentRelation the parent relation 
    79      * @return 
    80      */ 
    81     public static String explainDeletedRelationStillInUse(OsmApiException e, long childRelation, long parentRelation) { 
    82         String msg = tr( 
    83                 "<html><strong>Failed</strong> to delete <strong>relation {0}</strong>." 
    84                 + " It is still referred to by relation {1}.<br>" 
    85                 + "Please load relation {1}, remove the reference to relation {0}, and upload again.</html>", 
    86                 childRelation,parentRelation 
    87         ); 
    88         return msg; 
    89     } 
    90      
    91     /** 
    92      * Explains a precondition exception when a child node could not be deleted because 
    93      * it is still referred to by undeleted parent ways. 
    94      * 
    95      * @param e the exception 
    96      * @param childNode the child node 
    97      * @param parentWays the parent ways 
    98      * @return 
    99      */ 
    100     public static String explainDeletedNodeStillInUse(OsmApiException e, long childNode, Collection<Long> parentWays) { 
    101         String ids = parentWays.size() == 1 ? parentWays.iterator().next().toString() : parentWays.toString(); 
    102         String msg = trn( 
    103                 "<html><strong>Failed</strong> to delete <strong>node {0}</strong>." 
    104                 + " It is still referred to by way {1}.<br>" 
    105                 + "Please load the way, remove the reference to the node, and upload again.</html>", 
    106                 "<html><strong>Failed</strong> to delete <strong>node {0}</strong>." 
    107                 + " It is still referred to by ways {1}.<br>" 
    108                 + "Please load the ways, remove the reference to the node, and upload again.</html>", 
    109                 parentWays.size(), childNode, ids 
    110         ); 
    111         return msg; 
     77    public static Pair<OsmPrimitive, Collection<OsmPrimitive>> parsePreconditionFailed(String msg) { 
     78        final String ids = "(\\d+(?:,\\d+)*)"; 
     79        final Collection<OsmPrimitive> refs = new TreeSet<OsmPrimitive>(); // error message can contain several times the same way 
     80        Matcher m; 
     81        m = Pattern.compile(".*Node (\\d+) is still used by relations " + ids + ".*").matcher(msg); 
     82        if (m.matches()) { 
     83            OsmPrimitive n = new Node(Long.parseLong(m.group(1))); 
     84            for (String s : m.group(2).split(",")) { 
     85                refs.add(new Relation(Long.parseLong(m.group(2)))); 
     86            } 
     87            return Pair.create(n, refs); 
     88        } 
     89        m = Pattern.compile(".*Node (\\d+) is still used by ways " + ids + ".*").matcher(msg); 
     90        if (m.matches()) { 
     91            OsmPrimitive n = new Node(Long.parseLong(m.group(1))); 
     92            for (String s : m.group(2).split(",")) { 
     93                refs.add(new Way(Long.parseLong(m.group(2)))); 
     94            } 
     95            return Pair.create(n, refs); 
     96        } 
     97        m = Pattern.compile(".*The relation (\\d+) is used in relations? " + ids + ".*").matcher(msg); 
     98        if (m.matches()) { 
     99            OsmPrimitive n = new Relation(Long.parseLong(m.group(1))); 
     100            for (String s : m.group(2).split(",")) { 
     101                refs.add(new Relation(Long.parseLong(m.group(2)))); 
     102            } 
     103            return Pair.create(n, refs); 
     104        } 
     105        m = Pattern.compile(".*Way (\\d+) is still used by relations " + ids + ".*").matcher(msg); 
     106        if (m.matches()) { 
     107            OsmPrimitive n = new Way(Long.parseLong(m.group(1))); 
     108            for (String s : m.group(2).split(",")) { 
     109                refs.add(new Relation(Long.parseLong(m.group(2)))); 
     110            } 
     111            return Pair.create(n, refs); 
     112        } 
     113        m = Pattern.compile(".*Way (\\d+) requires the nodes with id in " + ids + ".*").matcher(msg); // ... ", which either do not exist, or are not visible" 
     114        if (m.matches()) { 
     115            OsmPrimitive n = new Way(Long.parseLong(m.group(1))); 
     116            for (String s : m.group(2).split(",")) { 
     117                refs.add(new Node(Long.parseLong(m.group(2)))); 
     118            } 
     119            return Pair.create(n, refs); 
     120        } 
     121        return null; 
    112122    } 
    113123 
     
    120130        e.printStackTrace(); 
    121131        String msg = e.getErrorHeader(); 
    122         if (msg != null) { 
    123             Matcher m = Pattern.compile("Precondition failed: The relation (\\d+) is used in relation (\\d+)\\.").matcher(msg); 
    124             if (m.matches()) { 
    125                 long childRelation = Long.parseLong(m.group(1)); 
    126                 long parentRelation = Long.parseLong(m.group(2)); 
    127                 return explainDeletedRelationStillInUse(e, childRelation, parentRelation); 
     132        Pair<OsmPrimitive, Collection<OsmPrimitive>> conflict = parsePreconditionFailed(e.getErrorHeader()); 
     133        if (conflict != null) { 
     134            OsmPrimitive firstRefs = conflict.b.iterator().next(); 
     135            Long objId = conflict.a.getId(); 
     136            Collection<Long> refIds= Utils.transform(conflict.b, new Utils.Function<OsmPrimitive, Long>() { 
     137 
     138                @Override 
     139                public Long apply(OsmPrimitive x) { 
     140                    return x.getId(); 
     141                } 
     142            }); 
     143            String refIdsString = refIds.size() == 1 ? refIds.iterator().next().toString() : refIds.toString(); 
     144            if (conflict.a instanceof Node) { 
     145                if (firstRefs instanceof Node) { 
     146                    return "<html>" + trn( 
     147                            "<strong>Failed</strong> to delete <strong>node {0}</strong>." 
     148                            + " It is still referred to by node {1}.<br>" 
     149                            + "Please load the node, remove the reference to the node, and upload again.", 
     150                            "<strong>Failed</strong> to delete <strong>node {0}</strong>." 
     151                            + " It is still referred to by nodes {1}.<br>" 
     152                            + "Please load the nodes, remove the reference to the node, and upload again.", 
     153                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     154                } else if (firstRefs instanceof Way) { 
     155                    return "<html>" + trn( 
     156                            "<strong>Failed</strong> to delete <strong>node {0}</strong>." 
     157                            + " It is still referred to by way {1}.<br>" 
     158                            + "Please load the way, remove the reference to the node, and upload again.", 
     159                            "<strong>Failed</strong> to delete <strong>node {0}</strong>." 
     160                            + " It is still referred to by ways {1}.<br>" 
     161                            + "Please load the ways, remove the reference to the node, and upload again.", 
     162                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     163                } else if (firstRefs instanceof Relation) { 
     164                    return "<html>" + trn( 
     165                            "<strong>Failed</strong> to delete <strong>node {0}</strong>." 
     166                            + " It is still referred to by relation {1}.<br>" 
     167                            + "Please load the relation, remove the reference to the node, and upload again.", 
     168                            "<strong>Failed</strong> to delete <strong>node {0}</strong>." 
     169                            + " It is still referred to by relations {1}.<br>" 
     170                            + "Please load the relations, remove the reference to the node, and upload again.", 
     171                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     172                } else { 
     173                    throw new IllegalStateException(); 
     174                } 
     175            } else if (conflict.a instanceof Way) { 
     176                if (firstRefs instanceof Node) { 
     177                    return "<html>" + trn( 
     178                            "<strong>Failed</strong> to delete <strong>way {0}</strong>." 
     179                            + " It is still referred to by node {1}.<br>" 
     180                            + "Please load the node, remove the reference to the way, and upload again.", 
     181                            "<strong>Failed</strong> to delete <strong>way {0}</strong>." 
     182                            + " It is still referred to by nodes {1}.<br>" 
     183                            + "Please load the nodes, remove the reference to the way, and upload again.", 
     184                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     185                } else if (firstRefs instanceof Way) { 
     186                    return "<html>" + trn( 
     187                            "<strong>Failed</strong> to delete <strong>way {0}</strong>." 
     188                            + " It is still referred to by way {1}.<br>" 
     189                            + "Please load the way, remove the reference to the way, and upload again.", 
     190                            "<strong>Failed</strong> to delete <strong>way {0}</strong>." 
     191                            + " It is still referred to by ways {1}.<br>" 
     192                            + "Please load the ways, remove the reference to the way, and upload again.", 
     193                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     194                } else if (firstRefs instanceof Relation) { 
     195                    return "<html>" + trn( 
     196                            "<strong>Failed</strong> to delete <strong>way {0}</strong>." 
     197                            + " It is still referred to by relation {1}.<br>" 
     198                            + "Please load the relation, remove the reference to the way, and upload again.", 
     199                            "<strong>Failed</strong> to delete <strong>way {0}</strong>." 
     200                            + " It is still referred to by relations {1}.<br>" 
     201                            + "Please load the relations, remove the reference to the way, and upload again.", 
     202                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     203                } else { 
     204                    throw new IllegalStateException(); 
     205                } 
     206            } else if (conflict.a instanceof Relation) { 
     207                if (firstRefs instanceof Node) { 
     208                    return "<html>" + trn( 
     209                            "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 
     210                            + " It is still referred to by node {1}.<br>" 
     211                            + "Please load the node, remove the reference to the relation, and upload again.", 
     212                            "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 
     213                            + " It is still referred to by nodes {1}.<br>" 
     214                            + "Please load the nodes, remove the reference to the relation, and upload again.", 
     215                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     216                } else if (firstRefs instanceof Way) { 
     217                    return "<html>" + trn( 
     218                            "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 
     219                            + " It is still referred to by way {1}.<br>" 
     220                            + "Please load the way, remove the reference to the relation, and upload again.", 
     221                            "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 
     222                            + " It is still referred to by ways {1}.<br>" 
     223                            + "Please load the ways, remove the reference to the relation, and upload again.", 
     224                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     225                } else if (firstRefs instanceof Relation) { 
     226                    return "<html>" + trn( 
     227                            "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 
     228                            + " It is still referred to by relation {1}.<br>" 
     229                            + "Please load the relation, remove the reference to the relation, and upload again.", 
     230                            "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 
     231                            + " It is still referred to by relations {1}.<br>" 
     232                            + "Please load the relations, remove the reference to the relation, and upload again.", 
     233                            conflict.b.size(), objId, refIdsString) + "</html>"; 
     234                } else { 
     235                    throw new IllegalStateException(); 
     236                } 
     237            } else { 
     238                throw new IllegalStateException(); 
    128239            } 
    129             m = Pattern.compile("Precondition failed: Node (\\d+) is still used by ways (\\d+(?:,\\d+)*)\\.").matcher(msg); 
    130             if (m.matches()) { 
    131                 long childNode = Long.parseLong(m.group(1)); 
    132                 Set<Long> parentWays = new TreeSet<Long>(); // Error message can contain several times the same way 
    133                 for (String s : m.group(2).split(",")) { 
    134                     parentWays.add(Long.parseLong(s)); 
    135                 } 
    136                 return explainDeletedNodeStillInUse(e, childNode, parentWays); 
    137             } 
    138         } 
    139         msg = tr( 
    140                 "<html>Uploading to the server <strong>failed</strong> because your current<br>" 
    141                 + "dataset violates a precondition.<br>" + "The error message is:<br>" + "{0}" + "</html>",  
    142                 escapeReservedCharactersHTML(e.getMessage())); 
    143         return msg; 
     240        } else { 
     241            return tr( 
     242                    "<html>Uploading to the server <strong>failed</strong> because your current<br>" 
     243                    + "dataset violates a precondition.<br>" + "The error message is:<br>" + "{0}" + "</html>", 
     244                    escapeReservedCharactersHTML(e.getMessage())); 
     245        } 
    144246    } 
    145247 
     
    276378                if (closeDate == null) { 
    277379                    msg = tr( 
    278                             "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.</html>", 
     380                            "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.", 
    279381                            changesetId 
    280382                    ); 
     
    283385                    msg = tr( 
    284386                            "<html>Closing of changeset <strong>{0}</strong> failed<br>" 
    285                             +" because it has already been closed on {1}.</html>", 
     387                            +" because it has already been closed on {1}.", 
    286388                            changesetId, 
    287389                            dateFormat.format(closeDate) 
     
    295397                    msg 
    296398            ); 
    297         } 
    298         msg = tr( 
    299                 "<html>The server reported that it has detected a conflict.</html>" 
    300         ); 
     399        } else { 
     400            msg = tr( 
     401                    "<html>The server reported that it has detected a conflict."); 
     402        } 
    301403        return msg; 
    302404    } 
     
    313415        msg = tr( 
    314416                "<html>Failed to upload to changeset <strong>{0}</strong><br>" 
    315                 +"because it has already been closed on {1}.</html>", 
     417                +"because it has already been closed on {1}.", 
    316418                e.getChangesetId(), 
    317419                e.getClosedOn() == null ? "?" : dateFormat.format(e.getClosedOn()) 
     
    354456        String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>" 
    355457                + "for security reasons. This is most likely because you are running<br>" 
    356                 + "in an applet and because you did not load your applet from ''{1}''.</html>", apiUrl, host); 
     458                + "in an applet and because you did not load your applet from ''{1}''.", apiUrl, host); 
    357459        return message; 
    358460    } 
     
    369471        String apiUrl = e.getUrl(); 
    370472        String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 
    371                 + "Please check your internet connection.</html>", apiUrl); 
     473                + "Please check your internet connection.", apiUrl); 
    372474        e.printStackTrace(); 
    373475        return message; 
     
    417519        String apiUrl = e.getUrl(); 
    418520        String message = tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>" 
    419                 + "This is most likely a temporary problem. Please try again later.</html>", apiUrl); 
     521                + "This is most likely a temporary problem. Please try again later.", apiUrl); 
    420522        e.printStackTrace(); 
    421523        return message; 
     
    495597        String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 
    496598                + "Host name ''{1}'' could not be resolved. <br>" 
    497                 + "Please check the API URL in your preferences and your internet connection.</html>", apiUrl, host); 
     599                + "Please check the API URL in your preferences and your internet connection.", apiUrl, host); 
    498600        e.printStackTrace(); 
    499601        return message; 
  • trunk/src/org/openstreetmap/josm/tools/Utils.java

    r4668 r4816  
    2121import java.util.ArrayList; 
    2222import java.util.Collection; 
     23import java.util.Iterator; 
    2324import java.util.List; 
    2425 
     
    396397        return sorted; 
    397398    } 
     399 
     400    /** 
     401     * Represents a function that can be applied to objects of {@code A} and 
     402     * returns objects of {@code B}. 
     403     * @param <A> class of input objects 
     404     * @param <B> class of transformed objects 
     405     */ 
     406    public static interface Function<A, B> { 
     407 
     408        /** 
     409         * Applies the function on {@code x}. 
     410         * @param x an object of 
     411         * @return the transformed object 
     412         */ 
     413        B apply(A x); 
     414    } 
     415 
     416    /** 
     417     * Transforms the collection {@code c} into an unmodifiable collection and 
     418     * applies the {@link Function} {@code f} on each element upon access. 
     419     * @param <A> class of input collection 
     420     * @param <B> class of transformed collection 
     421     * @param c a collection 
     422     * @param f a function that transforms objects of {@code A} to objects of {@code B} 
     423     * @return the transformed unmodifiable collection 
     424     */ 
     425    public static <A, B> Collection<B> transform(final Collection<A> c, final Function<A, B> f) { 
     426        return new Collection<B>() { 
     427 
     428            @Override 
     429            public int size() { 
     430                return c.size(); 
     431            } 
     432 
     433            @Override 
     434            public boolean isEmpty() { 
     435                return c.isEmpty(); 
     436            } 
     437 
     438            @Override 
     439            public boolean contains(Object o) { 
     440                return c.contains(o); 
     441            } 
     442 
     443            @Override 
     444            public Object[] toArray() { 
     445                return c.toArray(); 
     446            } 
     447 
     448            @Override 
     449            public <T> T[] toArray(T[] a) { 
     450                return c.toArray(a); 
     451            } 
     452 
     453            @Override 
     454            public String toString() { 
     455                return c.toString(); 
     456            } 
     457 
     458            @Override 
     459            public Iterator<B> iterator() { 
     460                return new Iterator<B>() { 
     461 
     462                    private Iterator<A> it = c.iterator(); 
     463 
     464                    @Override 
     465                    public boolean hasNext() { 
     466                        return it.hasNext(); 
     467                    } 
     468 
     469                    @Override 
     470                    public B next() { 
     471                        return f.apply(it.next()); 
     472                    } 
     473 
     474                    @Override 
     475                    public void remove() { 
     476                        throw new UnsupportedOperationException(); 
     477                    } 
     478                }; 
     479            } 
     480 
     481            @Override 
     482            public boolean add(B e) { 
     483                throw new UnsupportedOperationException(); 
     484            } 
     485 
     486            @Override 
     487            public boolean remove(Object o) { 
     488                throw new UnsupportedOperationException(); 
     489            } 
     490 
     491            @Override 
     492            public boolean containsAll(Collection<?> c) { 
     493                throw new UnsupportedOperationException(); 
     494            } 
     495 
     496            @Override 
     497            public boolean addAll(Collection<? extends B> c) { 
     498                throw new UnsupportedOperationException(); 
     499            } 
     500 
     501            @Override 
     502            public boolean removeAll(Collection<?> c) { 
     503                throw new UnsupportedOperationException(); 
     504            } 
     505 
     506            @Override 
     507            public boolean retainAll(Collection<?> c) { 
     508                throw new UnsupportedOperationException(); 
     509            } 
     510 
     511            @Override 
     512            public void clear() { 
     513                throw new UnsupportedOperationException(); 
     514            } 
     515        }; 
     516    } 
    398517} 
Note: See TracChangeset for help on using the changeset viewer.