53 private static final String TRUNCATION_FIELD = "<truncation>"; |
53 private static final String TRUNCATION_FIELD = "<truncation>"; |
54 |
54 |
55 // For encoding to Properties String |
55 // For encoding to Properties String |
56 private static final String RECORD_SEPARATOR = "\u241E"; |
56 private static final String RECORD_SEPARATOR = "\u241E"; |
57 |
57 |
58 // Current mode |
58 // Current mode -- initial value is placeholder during start-up |
59 private Mode mode = new Mode("", false); // initial value placeholder during start-up |
59 private Mode mode = new Mode(""); |
|
60 |
|
61 // Retained current mode -- for checks |
|
62 private Mode retainedCurrentMode = null; |
60 |
63 |
61 // Mapping of mode name to mode |
64 // Mapping of mode name to mode |
62 private final Map<String, Mode> modeMap = new HashMap<>(); |
65 private final Map<String, Mode> modeMap = new HashMap<>(); |
63 |
66 |
64 // Mapping of mode names to encoded retained mode |
67 // Mapping of mode names to encoded retained mode |
133 public String retainMode(MessageHandler messageHandler, ArgTokenizer at) { |
136 public String retainMode(MessageHandler messageHandler, ArgTokenizer at) { |
134 return new Setter(messageHandler, at).retainMode(); |
137 return new Setter(messageHandler, at).retainMode(); |
135 } |
138 } |
136 |
139 |
137 public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) { |
140 public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) { |
138 return new Setter(messageHandler, new ArgTokenizer("")).restoreEncodedModes(encoded); |
141 return new Setter(messageHandler, new ArgTokenizer("<init>", "")).restoreEncodedModes(encoded); |
139 } |
142 } |
140 |
143 |
141 public void markModesReadOnly() { |
144 public void markModesReadOnly() { |
142 modeMap.values().stream() |
145 modeMap.values().stream() |
143 .forEach(m -> m.readOnly = true); |
146 .forEach(m -> m.readOnly = true); |
190 * Set up an empty mode. |
193 * Set up an empty mode. |
191 * |
194 * |
192 * @param name |
195 * @param name |
193 * @param commandFluff True if should display command fluff messages |
196 * @param commandFluff True if should display command fluff messages |
194 */ |
197 */ |
195 Mode(String name, boolean commandFluff) { |
198 Mode(String name) { |
196 this.name = name; |
199 this.name = name; |
197 this.commandFluff = commandFluff; |
200 this.cases = new HashMap<>(); |
198 cases = new HashMap<>(); |
|
199 add("name", new Setting(ALWAYS, "%1$s")); |
201 add("name", new Setting(ALWAYS, "%1$s")); |
200 add("type", new Setting(ALWAYS, "%2$s")); |
202 add("type", new Setting(ALWAYS, "%2$s")); |
201 add("value", new Setting(ALWAYS, "%3$s")); |
203 add("value", new Setting(ALWAYS, "%3$s")); |
202 add("unresolved", new Setting(ALWAYS, "%4$s")); |
204 add("unresolved", new Setting(ALWAYS, "%4$s")); |
203 add("errors", new Setting(ALWAYS, "%5$s")); |
205 add("errors", new Setting(ALWAYS, "%5$s")); |
213 |
215 |
214 /** |
216 /** |
215 * Set up a copied mode. |
217 * Set up a copied mode. |
216 * |
218 * |
217 * @param name |
219 * @param name |
218 * @param commandFluff True if should display command fluff messages |
|
219 * @param m Mode to copy, or null for no fresh |
220 * @param m Mode to copy, or null for no fresh |
220 */ |
221 */ |
221 Mode(String name, boolean commandFluff, Mode m) { |
222 Mode(String name, Mode m) { |
222 this.name = name; |
223 this.name = name; |
223 this.commandFluff = commandFluff; |
224 this.commandFluff = m.commandFluff; |
224 cases = new HashMap<>(); |
225 this.prompt = m.prompt; |
225 |
226 this.continuationPrompt = m.continuationPrompt; |
|
227 this.cases = new HashMap<>(); |
226 m.cases.entrySet().stream() |
228 m.cases.entrySet().stream() |
227 .forEach(fes -> fes.getValue() |
229 .forEach(fes -> fes.getValue() |
228 .forEach(ing -> add(fes.getKey(), ing))); |
230 .forEach(ing -> add(fes.getKey(), ing))); |
229 |
231 |
230 this.prompt = m.prompt; |
|
231 this.continuationPrompt = m.continuationPrompt; |
|
232 } |
232 } |
233 |
233 |
234 /** |
234 /** |
235 * Set up a mode reconstituted from a preferences string. |
235 * Set up a mode reconstituted from a preferences string. |
236 * |
236 * |
680 fluffmsg("jshell.msg.see", "/help /set prompt"); |
690 fluffmsg("jshell.msg.see", "/help /set prompt"); |
681 } |
691 } |
682 return valid; |
692 return valid; |
683 } |
693 } |
684 |
694 |
685 // For /set newmode <new-mode> [-command|-quiet [<old-mode>]] |
695 /** |
686 boolean setNewMode() { |
696 * Set mode. Create, changed, or delete a feedback mode. For @{code /set |
687 String umode = at.next(); |
697 * mode <mode> [<old-mode>] [-command|-quiet|-delete]}. |
688 if (umode == null || !at.isIdentifier()) { |
698 * |
689 errorat("jshell.err.feedback.expected.new.feedback.mode"); |
699 * @return true if successful |
690 valid = false; |
700 */ |
691 } |
701 boolean setMode() { |
692 if (modeMap.containsKey(umode)) { |
702 at.allowedOptions("-command", "-quiet", "-delete"); |
693 errorat("jshell.err.feedback.expected.mode.name", umode); |
703 String umode = nextModeIdentifier(); |
694 valid = false; |
|
695 } |
|
696 String[] fluffOpt = at.next("-command", "-quiet"); |
|
697 boolean fluff = fluffOpt == null || fluffOpt.length != 1 || "-command".equals(fluffOpt[0]); |
|
698 if (fluffOpt != null && fluffOpt.length != 1) { |
|
699 errorat("jshell.err.feedback.command.quiet"); |
|
700 valid = false; |
|
701 } |
|
702 Mode om = null; |
704 Mode om = null; |
703 String omode = at.next(); |
705 String omode = at.next(); |
704 if (omode != null) { |
706 if (valid && omode != null) { |
705 om = toMode(omode); |
707 om = toMode(omode); |
706 } |
708 } |
|
709 checkOptionsAndRemainingInput(); |
|
710 boolean commandOption = at.hasOption("-command"); |
|
711 boolean quietOption = at.hasOption("-quiet"); |
|
712 boolean deleteOption = at.hasOption("-delete"); |
|
713 // Only one (or zero) of the options can be used |
|
714 if (valid && at.optionCount() > 1) { |
|
715 errorat("jshell.err.conflicting.options"); |
|
716 valid = false; |
|
717 } |
707 if (valid) { |
718 if (valid) { |
708 Mode nm = (om != null) |
719 Mode m = modeMap.get(umode); |
709 ? new Mode(umode, fluff, om) |
720 if (m != null && m.readOnly) { |
710 : new Mode(umode, fluff); |
721 // Cannot make changes to a the built-in modes |
711 modeMap.put(umode, nm); |
722 errorat("jshell.err.not.valid.with.predefined.mode", m.name); |
712 fluffmsg("jshell.msg.feedback.new.mode", nm.name); |
723 valid = false; |
713 } else { |
724 } else if (deleteOption) { |
714 fluffmsg("jshell.msg.see", "/help /set newmode"); |
725 if (m == null) { |
|
726 // Cannot delete a mode that does not exist |
|
727 errorat("jshell.err.mode.unknown", umode); |
|
728 valid = false; |
|
729 } else if (mode.name.equals(m.name)) { |
|
730 // Cannot delete the current mode out from under us |
|
731 errorat("jshell.err.cannot.delete.current.mode", umode); |
|
732 valid = false; |
|
733 } else { |
|
734 // Remove the mode |
|
735 modeMap.remove(umode); |
|
736 } |
|
737 } else { |
|
738 if (om != null || m == null) { |
|
739 // We are copying and existing mode and/or creating a |
|
740 // brand-new mode -- in either case create from scratch |
|
741 m = (om != null) |
|
742 ? new Mode(umode, om) |
|
743 : new Mode(umode); |
|
744 modeMap.put(umode, m); |
|
745 fluffmsg("jshell.msg.feedback.new.mode", m.name); |
|
746 // Set the current mode by name, in case we just smashed |
|
747 // the current mode |
|
748 if (umode.equals(mode.name)) { |
|
749 mode = modeMap.get(mode.name); |
|
750 } |
|
751 } |
|
752 if (commandOption || quietOption || om == null) { |
|
753 // set command fluff, if explicit, or wholly new |
|
754 m.setCommandFluff(!quietOption); |
|
755 } |
|
756 } |
|
757 } |
|
758 if (!valid) { |
|
759 fluffmsg("jshell.msg.see", "/help /set mode"); |
715 } |
760 } |
716 return valid; |
761 return valid; |
717 } |
762 } |
718 |
763 |
719 // For /set feedback <mode> |
764 // For /set feedback <mode> |
770 } |
813 } |
771 |
814 |
772 String retainFeedback() { |
815 String retainFeedback() { |
773 String umode = at.next(); |
816 String umode = at.next(); |
774 if (umode != null) { |
817 if (umode != null) { |
775 Mode m = toMode(umode); |
818 toModeIdentifier(umode); |
|
819 Mode m = valid ? toMode(umode) : null; |
776 if (valid && !m.readOnly && !retainedMap.containsKey(m.name)) { |
820 if (valid && !m.readOnly && !retainedMap.containsKey(m.name)) { |
777 errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined"); |
821 errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined"); |
778 valid = false; |
822 valid = false; |
779 } |
823 } |
780 if (valid) { |
824 if (valid) { |
781 mode = m; |
825 mode = m; |
|
826 retainedCurrentMode = m; |
782 fluffmsg("jshell.msg.feedback.mode", mode.name); |
827 fluffmsg("jshell.msg.feedback.mode", mode.name); |
783 } else { |
828 } else { |
784 fluffmsg("jshell.msg.see", "/help /retain feedback"); |
829 fluffmsg("jshell.msg.see", "/help /retain feedback"); |
785 return null; |
830 return null; |
786 } |
831 } |
787 } |
832 } |
788 return mode.name; |
833 return mode.name; |
789 } |
834 } |
790 |
835 |
|
836 /** |
|
837 * Retain (or delete from retention) a previously set mode. |
|
838 * |
|
839 * @return all retained modes encoded into a String |
|
840 */ |
791 String retainMode() { |
841 String retainMode() { |
792 Mode m = nextMode(); |
842 at.allowedOptions("-delete"); |
793 if (valid && m.readOnly) { |
843 String umode = nextModeIdentifier(); |
794 errorat("jshell.err.not.valid.with.predefined.mode", m.name); |
844 // -delete is the only valid option, fail for anything else |
|
845 checkOptionsAndRemainingInput(); |
|
846 boolean deleteOption = at.hasOption("-delete"); |
|
847 // Lookup the mode |
|
848 Mode m; |
|
849 if (!valid) { |
|
850 m = null; |
|
851 // Skip this stuff, we have failed already |
|
852 } else if (deleteOption) { |
|
853 // If delete, allow for deleting, from retention, a mode that |
|
854 // has been locally deleted but is retained. |
|
855 // Also require the full name. |
|
856 m = modeMap.get(umode); |
|
857 if (m == null && !retainedMap.containsKey(umode)) { |
|
858 errorat("jshell.err.mode.unknown", umode); |
|
859 valid = false; |
|
860 } |
|
861 } else { |
|
862 // For retain do normal lookup and checking |
|
863 m = toMode(umode); |
|
864 } |
|
865 |
|
866 // Built-in modes cannot be retained or deleted |
|
867 if (valid && m != null && m.readOnly) { |
|
868 errorat("jshell.err.not.valid.with.predefined.mode", umode); |
795 valid = false; |
869 valid = false; |
796 } |
870 } |
797 if (valid) { |
871 if (valid) { |
798 retainedMap.put(m.name, m.encode()); |
872 if (deleteOption) { |
|
873 if (mode.name.equals(umode)) { |
|
874 // Cannot delete the current mode out from under us |
|
875 errorat("jshell.err.cannot.delete.current.mode", umode); |
|
876 valid = false; |
|
877 } else if (retainedCurrentMode != null && retainedCurrentMode.name.equals(umode)) { |
|
878 // Cannot delete the retained mode or re-start has error |
|
879 errorat("jshell.err.cannot.delete.retained.mode", umode); |
|
880 valid = false; |
|
881 } else { |
|
882 // Delete the mode |
|
883 modeMap.remove(umode); |
|
884 retainedMap.remove(umode); |
|
885 } |
|
886 } else { |
|
887 // Retain the current encoding |
|
888 retainedMap.put(m.name, m.encode()); |
|
889 } |
|
890 } |
|
891 if (valid) { |
|
892 // Join all the retained encodings |
799 return String.join(RECORD_SEPARATOR, retainedMap.values()); |
893 return String.join(RECORD_SEPARATOR, retainedMap.values()); |
800 } else { |
894 } else { |
801 fluffmsg("jshell.msg.see", "/help /retain mode"); |
895 fluffmsg("jshell.msg.see", "/help /retain mode"); |
802 return null; |
896 return null; |
803 } |
897 } |
804 } |
898 } |
805 |
899 |
806 boolean restoreEncodedModes(String allEncoded) { |
900 boolean restoreEncodedModes(String allEncoded) { |
807 // Iterate over each record in each encoded mode |
901 try { |
808 String[] ms = allEncoded.split(RECORD_SEPARATOR); |
902 // Iterate over each record in each encoded mode |
809 Iterator<String> itr = Arrays.asList(ms).iterator(); |
903 String[] ms = allEncoded.split(RECORD_SEPARATOR); |
810 while (itr.hasNext()) { |
904 Iterator<String> itr = Arrays.asList(ms).iterator(); |
811 // Reconstruct the encoded mode |
905 while (itr.hasNext()) { |
812 Mode m = new Mode(itr); |
906 // Reconstruct the encoded mode |
813 modeMap.put(m.name, m); |
907 Mode m = new Mode(itr); |
814 // Continue to retain it a new retains occur |
908 modeMap.put(m.name, m); |
815 retainedMap.put(m.name, m.encode()); |
909 // Continue to retain it a new retains occur |
816 } |
910 retainedMap.put(m.name, m.encode()); |
817 return true; |
911 } |
|
912 return true; |
|
913 } catch (Throwable exc) { |
|
914 // Catastrophic corruption -- clear map |
|
915 errorat("jshell.err.retained.mode.failure", exc); |
|
916 retainedMap.clear(); |
|
917 return false; |
|
918 } |
818 } |
919 } |
819 |
920 |
820 // install the format of a field under parsed selectors |
921 // install the format of a field under parsed selectors |
821 boolean installFormat(Mode m, String field, String format, String help) { |
922 boolean installFormat(Mode m, String field, String format, String help) { |
822 String slRaw; |
923 String slRaw; |
842 fluffmsg("jshell.msg.see", help); |
943 fluffmsg("jshell.msg.see", help); |
843 } |
944 } |
844 return valid; |
945 return valid; |
845 } |
946 } |
846 |
947 |
|
948 void checkOptionsAndRemainingInput() { |
|
949 if (!valid) { |
|
950 return; |
|
951 } |
|
952 String junk = at.remainder(); |
|
953 if (!junk.isEmpty()) { |
|
954 errorat("jshell.err.unexpected.at.end", junk); |
|
955 valid = false; |
|
956 } else { |
|
957 String bad = at.badOptions(); |
|
958 if (!bad.isEmpty()) { |
|
959 errorat("jshell.err.unknown.option", bad); |
|
960 valid = false; |
|
961 } |
|
962 } |
|
963 } |
|
964 |
|
965 /** |
|
966 * Check that the specified string is an identifier (Java identifier). |
|
967 * If null display the missing error. If it is not an identifier, |
|
968 * display the error. |
|
969 * |
|
970 * @param id the string to check, MUST be the most recently retrieved |
|
971 * token from 'at'. |
|
972 * @param missing the resource error to display if null |
|
973 * @param err the resource error to display if not an identifier |
|
974 * @return the identifier string, or null if null or not an identifier |
|
975 */ |
|
976 String toIdentifier(String id, String missing, String err) { |
|
977 if (id == null) { |
|
978 errorat(missing); |
|
979 valid = false; |
|
980 return null; |
|
981 } |
|
982 if (at.isQuoted() || |
|
983 !id.codePoints().allMatch(cp -> Character.isJavaIdentifierPart(cp))) { |
|
984 errorat(err, id); |
|
985 valid = false; |
|
986 return null; |
|
987 } |
|
988 return id; |
|
989 } |
|
990 |
|
991 String toModeIdentifier(String id) { |
|
992 return toIdentifier(id, "jshell.err.missing.mode", "jshell.err.mode.name"); |
|
993 } |
|
994 |
|
995 String nextModeIdentifier() { |
|
996 return toModeIdentifier(at.next()); |
|
997 } |
|
998 |
847 Mode nextMode() { |
999 Mode nextMode() { |
848 String umode = at.next(); |
1000 String umode = nextModeIdentifier(); |
849 return toMode(umode); |
1001 return toMode(umode); |
850 } |
1002 } |
851 |
1003 |
852 Mode toMode(String umode) { |
1004 Mode toMode(String umode) { |
853 if (umode == null || !at.isIdentifier()) { |
1005 if (!valid) { |
854 errorat("jshell.err.feedback.expected.mode"); |
1006 return null; |
|
1007 } |
|
1008 if (umode == null) { |
|
1009 errorat("jshell.err.missing.mode"); |
855 valid = false; |
1010 valid = false; |
856 return null; |
1011 return null; |
857 } |
1012 } |
858 Mode m = modeMap.get(umode); |
1013 Mode m = modeMap.get(umode); |
859 if (m != null) { |
1014 if (m != null) { |