1 19 20 package org.netbeans.modules.editor.html; 21 22 import java.awt.datatransfer.DataFlavor ; 23 import java.awt.datatransfer.Transferable ; 24 import java.awt.datatransfer.UnsupportedFlavorException ; 25 import java.awt.event.ActionEvent ; 26 import java.awt.im.InputContext ; 27 import java.io.ByteArrayInputStream ; 28 import java.io.IOException ; 29 import java.io.InputStream ; 30 import java.io.Reader ; 31 import java.io.StringReader ; 32 import java.io.StringWriter ; 33 import java.util.logging.Level ; 34 import java.util.logging.Logger ; 35 import javax.swing.Action ; 36 import javax.swing.JComponent ; 37 import javax.swing.JEditorPane ; 38 import javax.swing.JMenu ; 39 import javax.swing.JPasswordField ; 40 import javax.swing.TransferHandler ; 41 import javax.swing.plaf.UIResource ; 42 import javax.swing.text.BadLocationException ; 43 import javax.swing.text.Caret ; 44 import javax.swing.text.Document ; 45 import javax.swing.text.EditorKit ; 46 import javax.swing.text.Position ; 47 import javax.swing.text.TextAction ; 48 import javax.swing.text.JTextComponent ; 49 import org.netbeans.api.editor.fold.FoldHierarchy; 50 import org.netbeans.api.editor.fold.FoldUtilities; 51 import org.netbeans.api.lexer.TokenHierarchy; 52 import org.netbeans.editor.*; 53 import org.netbeans.editor.BaseKit.DeleteCharAction; 54 import org.netbeans.editor.ext.*; 55 import org.netbeans.editor.ext.ExtKit.ExtDefaultKeyTypedAction; 56 import org.netbeans.editor.ext.html.*; 57 import org.netbeans.editor.ext.html.HTMLSyntaxSupport; 58 import org.netbeans.editor.ext.html.parser.SyntaxParser; 59 import org.netbeans.modules.editor.NbEditorKit; 60 import org.netbeans.modules.editor.NbEditorKit.GenerateFoldPopupAction; 61 import org.netbeans.modules.html.editor.coloring.EmbeddingUpdater; 62 import org.netbeans.modules.html.editor.folding.HTMLFoldTypes; 63 import org.openide.util.NbBundle; 64 65 71 72 public class HTMLKit extends NbEditorKit implements org.openide.util.HelpCtx.Provider { 73 74 public org.openide.util.HelpCtx getHelpCtx() { 75 return new org.openide.util.HelpCtx(HTMLKit.class); 76 } 77 78 private static final Logger LOGGER = Logger.getLogger(HTMLKit.class.getName()); 79 80 static final long serialVersionUID =-1381945567613910297L; 81 82 public static final String HTML_MIME_TYPE = "text/html"; 84 public static final String shiftInsertBreakAction = "shift-insert-break"; 86 public static final String collapseAllCommentsAction = "collapse-all-comment-folds"; 89 public static final String expandAllCommentsAction = "expand-all-comment-folds"; 91 private static boolean setupReadersInitialized = false; 92 93 public HTMLKit(){ 94 if (!setupReadersInitialized){ 95 NbReaderProvider.setupReaders(); 96 setupReadersInitialized = true; 97 } 98 } 99 100 public String getContentType() { 101 return HTML_MIME_TYPE; 102 } 103 104 public CompletionJavaDoc createCompletionJavaDoc(ExtEditorUI extEditorUI) { 105 return null; 106 } 107 108 protected void initDocument(final BaseDocument doc) { 109 TokenHierarchy hi = TokenHierarchy.get(doc); 110 if(hi == null) { 111 LOGGER.log(Level.WARNING, "TokenHierarchy is null for document " + doc); 112 return ; 113 } 114 115 SyntaxParser.get(doc).addSyntaxParserListener(new EmbeddingUpdater(doc)); 117 } 118 119 123 public Syntax createSyntax(Document doc) { 124 return new HTMLSyntax(); 125 } 126 127 128 public SyntaxSupport createSyntaxSupport(BaseDocument doc) { 129 return new HTMLSyntaxSupport(doc); 130 } 131 132 133 public Completion createCompletion(ExtEditorUI extEditorUI) { 134 return null; 135 } 136 137 public Formatter createFormatter() { 138 return new HTMLFormatter(this.getClass()); 139 } 140 141 142 public void install(javax.swing.JEditorPane c) { 143 super.install(c); 144 c.setTransferHandler(new HTMLTransferHandler()); 145 } 146 147 protected Action [] createActions() { 148 Action [] HTMLActions = new Action [] { 149 new HTMLDefaultKeyTypedAction(), 150 new HTMLDeleteCharAction(deletePrevCharAction, false), 151 new HTMLDeleteCharAction(deleteNextCharAction, true), 152 new HTMLShiftBreakAction(), 153 new MatchBraceAction(ExtKit.matchBraceAction, false), 155 new MatchBraceAction(ExtKit.selectionMatchBraceAction, true), 156 new HTMLGenerateFoldPopupAction(), 157 new CollapseAllCommentsFolds(), 158 new ExpandAllCommentsFolds() 159 }; 160 return TextAction.augmentList(super.createActions(), HTMLActions); 161 } 162 163 public static class HTMLDefaultKeyTypedAction extends ExtDefaultKeyTypedAction { 164 165 protected void insertString(BaseDocument doc, int dotPos, 166 Caret caret, String str, 167 boolean overwrite) throws BadLocationException { 168 super.insertString(doc, dotPos, caret, str, overwrite); 169 HTMLAutoCompletion.charInserted(doc, dotPos, caret, str.charAt(0)); 170 } 171 172 } 173 174 public static class HTMLDeleteCharAction extends DeleteCharAction { 175 176 public HTMLDeleteCharAction(String name, boolean nextChar) { 177 super(name, nextChar); 178 } 179 180 protected void charBackspaced(BaseDocument doc, int dotPos, Caret caret, char ch) throws BadLocationException { 181 super.charBackspaced(doc, dotPos, caret, ch); 182 HTMLAutoCompletion.charDeleted(doc, dotPos, caret, ch); 183 } 184 } 185 186 187 public static class HTMLShiftBreakAction extends BaseAction { 188 189 static final long serialVersionUID =4004043376345356061L; 190 191 public HTMLShiftBreakAction() { 192 super( shiftInsertBreakAction, ABBREV_RESET 193 | MAGIC_POSITION_RESET | UNDO_MERGE_RESET); 194 } 195 196 public void actionPerformed(ActionEvent evt, JTextComponent target) { 197 if (target != null) { 198 Completion completion = ExtUtilities.getCompletion(target); 199 if (completion != null && completion.isPaneVisible()) { 200 if (completion.substituteText( true )) { 201 } else { 203 completion.refresh(false); 204 } 205 } 206 } 207 } 208 209 } 210 211 213 public static class MatchBraceAction extends ExtKit.MatchBraceAction { 214 215 private boolean select; 217 public MatchBraceAction(String name, boolean select) { 218 super(name, select); 219 this.select = select; 220 } 221 222 public void actionPerformed(ActionEvent evt, JTextComponent target) { 223 if (target != null) { 224 try { 225 Caret caret = target.getCaret(); 226 BaseDocument doc = Utilities.getDocument(target); 227 int dotPos = caret.getDot(); 228 ExtSyntaxSupport sup = (ExtSyntaxSupport)doc.getSyntaxSupport(); 229 230 if (dotPos > 0) { 232 int[] matchBlk = sup.findMatchingBlock(dotPos - 1, false); 233 if (matchBlk != null) { 234 if (select) { 235 caret.moveDot(matchBlk[1]); 236 } else { 237 caret.setDot(matchBlk[1]); 238 } 239 } 240 241 } else{ super.actionPerformed(evt, target); 243 } 244 245 } catch (BadLocationException e) { 246 target.getToolkit().beep(); 247 } 248 } 249 } 250 } 251 252 public static class HTMLGenerateFoldPopupAction extends GenerateFoldPopupAction { 253 254 protected void addAdditionalItems(JTextComponent target, JMenu menu){ 255 addAction(target, menu, collapseAllCommentsAction); 256 addAction(target, menu, expandAllCommentsAction); 257 } 258 } 259 260 public static class ExpandAllCommentsFolds extends BaseAction{ 261 public ExpandAllCommentsFolds(){ 262 super(expandAllCommentsAction); 263 putValue(SHORT_DESCRIPTION, NbBundle.getBundle(HTMLKit.class).getString("expand-all-comment-folds")); 264 putValue(BaseAction.POPUP_MENU_TEXT, NbBundle.getBundle(HTMLKit.class).getString("popup-expand-all-comment-folds")); 265 } 266 267 public void actionPerformed(ActionEvent evt, JTextComponent target) { 268 FoldHierarchy hierarchy = FoldHierarchy.get(target); 269 FoldUtilities.expand(hierarchy, HTMLFoldTypes.COMMENT); 271 } 272 } 273 274 public static class CollapseAllCommentsFolds extends BaseAction{ 275 public CollapseAllCommentsFolds(){ 276 super(collapseAllCommentsAction); 277 putValue(SHORT_DESCRIPTION, NbBundle.getBundle(HTMLKit.class).getString("collapse-all-comment-folds")); putValue(BaseAction.POPUP_MENU_TEXT, NbBundle.getBundle(HTMLKit.class).getString("popup-collapse-all-comment-folds")); } 280 281 public void actionPerformed(ActionEvent evt, JTextComponent target) { 282 FoldHierarchy hierarchy = FoldHierarchy.get(target); 283 FoldUtilities.collapse(hierarchy, HTMLFoldTypes.COMMENT); 285 } 286 } 287 288 289 290 291 292 299 static class HTMLTransferHandler extends TransferHandler implements UIResource { 300 301 private JTextComponent exportComp; 302 private boolean shouldRemove; 303 private int p0; 304 private int p1; 305 306 318 protected DataFlavor getImportFlavor(DataFlavor [] flavors, JTextComponent c) { 319 DataFlavor plainFlavor = null; 320 DataFlavor refFlavor = null; 321 DataFlavor stringFlavor = null; 322 323 if (c instanceof JEditorPane ) { 324 for (int i = 0; i < flavors.length; i++) { 325 String mime = flavors[i].getMimeType(); 326 if (mime.startsWith(((JEditorPane )c).getEditorKit().getContentType())) { 327 } else if (plainFlavor == null && mime.startsWith("text/plain")) { plainFlavor = flavors[i]; 330 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") && flavors[i].getRepresentationClass() == java.lang.String .class) { 332 refFlavor = flavors[i]; 333 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) { 334 stringFlavor = flavors[i]; 335 } 336 } 337 if (plainFlavor != null) { 338 return plainFlavor; 339 } else if (refFlavor != null) { 340 return refFlavor; 341 } else if (stringFlavor != null) { 342 return stringFlavor; 343 } 344 return null; 345 } 346 347 348 for (int i = 0; i < flavors.length; i++) { 349 String mime = flavors[i].getMimeType(); 350 if (mime.startsWith("text/plain")) { return flavors[i]; 352 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") && flavors[i].getRepresentationClass() == java.lang.String .class) { 354 refFlavor = flavors[i]; 355 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) { 356 stringFlavor = flavors[i]; 357 } 358 } 359 if (refFlavor != null) { 360 return refFlavor; 361 } else if (stringFlavor != null) { 362 return stringFlavor; 363 } 364 return null; 365 } 366 367 370 protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead) 371 throws BadLocationException , IOException { 372 if (useRead) { 373 int startPosition = c.getSelectionStart(); 374 int endPosition = c.getSelectionEnd(); 375 int length = endPosition - startPosition; 376 EditorKit kit = c.getUI().getEditorKit(c); 377 Document doc = c.getDocument(); 378 if (length > 0) { 379 doc.remove(startPosition, length); 380 } 381 kit.read(in, doc, startPosition); 382 } else { 383 char[] buff = new char[1024]; 384 int nch; 385 boolean lastWasCR = false; 386 int last; 387 StringBuffer sbuff = null; 388 389 while ((nch = in.read(buff, 0, buff.length)) != -1) { 392 if (sbuff == null) { 393 sbuff = new StringBuffer (nch); 394 } 395 last = 0; 396 for(int counter = 0; counter < nch; counter++) { 397 switch(buff[counter]) { 398 case '\r': 399 if (lastWasCR) { 400 if (counter == 0) { 401 sbuff.append('\n'); 402 } else { 403 buff[counter - 1] = '\n'; 404 } 405 } else { 406 lastWasCR = true; 407 } 408 break; 409 case '\n': 410 if (lastWasCR) { 411 if (counter > (last + 1)) { 412 sbuff.append(buff, last, counter - last - 1); 413 } 414 lastWasCR = false; 417 last = counter; 418 } 419 break; 420 default: 421 if (lastWasCR) { 422 if (counter == 0) { 423 sbuff.append('\n'); 424 } else { 425 buff[counter - 1] = '\n'; 426 } 427 lastWasCR = false; 428 } 429 break; 430 } 431 } 432 if (last < nch) { 433 if (lastWasCR) { 434 if (last < (nch - 1)) { 435 sbuff.append(buff, last, nch - last - 1); 436 } 437 } else { 438 sbuff.append(buff, last, nch - last); 439 } 440 } 441 } 442 if (lastWasCR) { 443 sbuff.append('\n'); 444 } 445 c.replaceSelection(sbuff != null ? sbuff.toString() : ""); } 447 } 448 449 451 463 public int getSourceActions(JComponent c) { 464 int actions = NONE; 465 if (! (c instanceof JPasswordField )) { 466 if (((JTextComponent )c).isEditable()) { 467 actions = COPY_OR_MOVE; 468 } else { 469 actions = COPY; 470 } 471 } 472 return actions; 473 } 474 475 484 protected Transferable createTransferable(JComponent comp) { 485 exportComp = (JTextComponent )comp; 486 shouldRemove = true; 487 p0 = exportComp.getSelectionStart(); 488 p1 = exportComp.getSelectionEnd(); 489 return (p0 != p1) ? (new HTMLTransferable(exportComp, p0, p1)) : null; 490 } 491 492 501 protected void exportDone(JComponent source, Transferable data, int action) { 502 if (shouldRemove && action == MOVE) { 505 HTMLTransferable t = (HTMLTransferable)data; 506 t.removeText(); 507 } 508 509 exportComp = null; 510 } 511 512 523 public boolean importData(JComponent comp, Transferable t) { 524 JTextComponent c = (JTextComponent )comp; 525 526 if (c == exportComp && c.getCaretPosition() >= p0 && c.getCaretPosition() <= p1) { 531 shouldRemove = false; 532 return true; 533 } 534 535 boolean imported = false; 536 DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c); 537 if (importFlavor != null) { 538 try { 539 boolean useRead = false; 540 if (comp instanceof JEditorPane ) { 541 JEditorPane ep = (JEditorPane )comp; 542 if (!ep.getContentType().startsWith("text/plain") && importFlavor.getMimeType().startsWith(ep.getContentType())) { 544 useRead = true; 545 } 546 } 547 InputContext ic = c.getInputContext(); 548 if (ic != null) { 549 ic.endComposition(); 550 } 551 Reader r = importFlavor.getReaderForText(t); 552 handleReaderImport(r, c, useRead); 553 imported = true; 554 } catch (UnsupportedFlavorException ufe) { 555 } catch (BadLocationException ble) { 557 } catch (IOException ioe) { 559 } 561 } 562 return imported; 563 } 564 565 575 public boolean canImport(JComponent comp, DataFlavor [] flavors) { 576 JTextComponent c = (JTextComponent )comp; 577 if (!(c.isEditable() && c.isEnabled())) { 578 return false; 579 } 580 return (getImportFlavor(flavors, c) != null); 581 } 582 583 592 static class HTMLTransferable extends BasicTransferable { 593 594 HTMLTransferable(JTextComponent c, int start, int end) { 595 super(null, null); 596 597 this.c = c; 598 599 Document doc = c.getDocument(); 600 601 try { 602 p0 = doc.createPosition(start); 603 p1 = doc.createPosition(end); 604 605 plainData = c.getSelectedText(); 606 607 if (c instanceof JEditorPane ) { 608 JEditorPane ep = (JEditorPane )c; 609 610 mimeType = ep.getContentType(); 611 612 if (mimeType.startsWith("text/plain")) { return; 614 } 615 616 StringWriter sw = new StringWriter (p1.getOffset() - p0.getOffset()); 617 ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset()); 618 619 if (mimeType.startsWith("text/html")) { htmlData = sw.toString(); 621 } else { 622 richText = sw.toString(); 623 } 624 } 625 } catch (BadLocationException ble) { 626 } catch (IOException ioe) { 627 } 628 } 629 630 void removeText() { 631 if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) { 632 try { 633 Document doc = c.getDocument(); 634 doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset()); 635 } catch (BadLocationException e) { 636 } 637 } 638 } 639 640 642 646 protected DataFlavor [] getRicherFlavors() { 647 if (richText == null) { 648 return null; 649 } 650 651 try { 652 DataFlavor [] flavors = new DataFlavor [3]; 653 flavors[0] = new DataFlavor (mimeType + ";class=java.lang.String"); flavors[1] = new DataFlavor (mimeType + ";class=java.io.Reader"); flavors[2] = new DataFlavor (mimeType + ";class=java.io.InputStream;charset=unicode"); return flavors; 657 } catch (ClassNotFoundException cle) { 658 } 660 661 return null; 662 } 663 664 667 protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException { 668 if (richText == null) { 669 return null; 670 } 671 672 if (String .class.equals(flavor.getRepresentationClass())) { 673 return richText; 674 } else if (Reader .class.equals(flavor.getRepresentationClass())) { 675 return new StringReader (richText); 676 } else if (InputStream .class.equals(flavor.getRepresentationClass())) { 677 return new ByteArrayInputStream (richText.getBytes()); 678 } 679 throw new UnsupportedFlavorException (flavor); 680 } 681 682 Position p0; 683 Position p1; 684 String mimeType; 685 String richText; 686 JTextComponent c; 687 } 688 689 } 690 691 private static class BasicTransferable implements Transferable , UIResource { 692 693 protected String plainData; 694 protected String htmlData; 695 696 private static DataFlavor [] htmlFlavors; 697 private static DataFlavor [] stringFlavors; 698 private static DataFlavor [] plainFlavors; 699 700 static { 701 try { 702 htmlFlavors = new DataFlavor [3]; 703 htmlFlavors[0] = new DataFlavor ("text/html;class=java.lang.String"); htmlFlavors[1] = new DataFlavor ("text/html;class=java.io.Reader"); htmlFlavors[2] = new DataFlavor ("text/html;charset=unicode;class=java.io.InputStream"); 707 plainFlavors = new DataFlavor [3]; 708 plainFlavors[0] = new DataFlavor ("text/plain;class=java.lang.String"); plainFlavors[1] = new DataFlavor ("text/plain;class=java.io.Reader"); plainFlavors[2] = new DataFlavor ("text/plain;charset=unicode;class=java.io.InputStream"); 712 stringFlavors = new DataFlavor [2]; 713 stringFlavors[0] = new DataFlavor (DataFlavor.javaJVMLocalObjectMimeType+";class=java.lang.String"); stringFlavors[1] = DataFlavor.stringFlavor; 715 716 } catch (ClassNotFoundException cle) { 717 System.err.println("error initializing javax.swing.plaf.basic.BasicTranserable"); } 719 } 720 721 public BasicTransferable(String plainData, String htmlData) { 722 this.plainData = plainData; 723 this.htmlData = htmlData; 724 } 725 726 727 733 public DataFlavor [] getTransferDataFlavors() { 734 DataFlavor [] richerFlavors = getRicherFlavors(); 735 int nRicher = (richerFlavors != null) ? richerFlavors.length : 0; 736 int nHTML = (isHTMLSupported()) ? htmlFlavors.length : 0; 737 int nPlain = (isPlainSupported()) ? plainFlavors.length: 0; 738 int nString = (isPlainSupported()) ? stringFlavors.length : 0; 739 int nFlavors = nRicher + nHTML + nPlain + nString; 740 DataFlavor [] flavors = new DataFlavor [nFlavors]; 741 742 int nDone = 0; 744 if (nRicher > 0) { 745 System.arraycopy(richerFlavors, 0, flavors, nDone, nRicher); 746 nDone += nRicher; 747 } 748 if (nHTML > 0) { 749 System.arraycopy(htmlFlavors, 0, flavors, nDone, nHTML); 750 nDone += nHTML; 751 } 752 if (nPlain > 0) { 753 System.arraycopy(plainFlavors, 0, flavors, nDone, nPlain); 754 nDone += nPlain; 755 } 756 if (nString > 0) { 757 System.arraycopy(stringFlavors, 0, flavors, nDone, nString); 758 nDone += nString; 759 } 760 return flavors; 761 } 762 763 769 public boolean isDataFlavorSupported(DataFlavor flavor) { 770 DataFlavor [] flavors = getTransferDataFlavors(); 771 for (int i = 0; i < flavors.length; i++) { 772 if (flavors[i].equals(flavor)) { 773 return true; 774 } 775 } 776 return false; 777 } 778 779 790 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException , IOException { 791 DataFlavor [] richerFlavors = getRicherFlavors(); 792 if (isRicherFlavor(flavor)) { 793 return getRicherData(flavor); 794 } else if (isHTMLFlavor(flavor)) { 795 String data = getHTMLData(); 796 data = (data == null) ? "" : data; if (String .class.equals(flavor.getRepresentationClass())) { 798 return data; 799 } else if (Reader .class.equals(flavor.getRepresentationClass())) { 800 return new StringReader (data); 801 } else if (InputStream .class.equals(flavor.getRepresentationClass())) { 802 return new ByteArrayInputStream (data.getBytes()); 803 } 804 } else if (isPlainFlavor(flavor)) { 806 String data = getPlainData(); 807 data = (data == null) ? "" : data; 808 if (String .class.equals(flavor.getRepresentationClass())) { 809 return data; 810 } else if (Reader .class.equals(flavor.getRepresentationClass())) { 811 return new StringReader (data); 812 } else if (InputStream .class.equals(flavor.getRepresentationClass())) { 813 return new ByteArrayInputStream (data.getBytes()); 814 } 815 817 } else if (isStringFlavor(flavor)) { 818 String data = getPlainData(); 819 data = (data == null) ? "" : data; return data; 821 } 822 throw new UnsupportedFlavorException (flavor); 823 } 824 825 827 protected boolean isRicherFlavor(DataFlavor flavor) { 828 DataFlavor [] richerFlavors = getRicherFlavors(); 829 int nFlavors = (richerFlavors != null) ? richerFlavors.length : 0; 830 for (int i = 0; i < nFlavors; i++) { 831 if (richerFlavors[i].equals(flavor)) { 832 return true; 833 } 834 } 835 return false; 836 } 837 838 843 protected DataFlavor [] getRicherFlavors() { 844 return null; 845 } 846 847 protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException { 848 return null; 849 } 850 851 853 859 protected boolean isHTMLFlavor(DataFlavor flavor) { 860 DataFlavor [] flavors = htmlFlavors; 861 for (int i = 0; i < flavors.length; i++) { 862 if (flavors[i].equals(flavor)) { 863 return true; 864 } 865 } 866 return false; 867 } 868 869 873 protected boolean isHTMLSupported() { 874 return htmlData != null; 875 } 876 877 880 protected String getHTMLData() { 881 return htmlData; 882 } 883 884 886 892 protected boolean isPlainFlavor(DataFlavor flavor) { 893 DataFlavor [] flavors = plainFlavors; 894 for (int i = 0; i < flavors.length; i++) { 895 if (flavors[i].equals(flavor)) { 896 return true; 897 } 898 } 899 return false; 900 } 901 902 906 protected boolean isPlainSupported() { 907 return plainData != null; 908 } 909 910 913 protected String getPlainData() { 914 return plainData; 915 } 916 917 919 925 protected boolean isStringFlavor(DataFlavor flavor) { 926 DataFlavor [] flavors = stringFlavors; 927 for (int i = 0; i < flavors.length; i++) { 928 if (flavors[i].equals(flavor)) { 929 return true; 930 } 931 } 932 return false; 933 } 934 935 936 } 937 938 940 941 } 942 943 | Popular Tags |