1 | package edu.ucsb.cs156.frontiers.controllers; | |
2 | ||
3 | import com.opencsv.CSVReader; | |
4 | import com.opencsv.exceptions.CsvException; | |
5 | import edu.ucsb.cs156.frontiers.entities.Course; | |
6 | import edu.ucsb.cs156.frontiers.entities.RosterStudent; | |
7 | import edu.ucsb.cs156.frontiers.entities.Team; | |
8 | import edu.ucsb.cs156.frontiers.entities.TeamMember; | |
9 | import edu.ucsb.cs156.frontiers.errors.EntityNotFoundException; | |
10 | import edu.ucsb.cs156.frontiers.repositories.CourseRepository; | |
11 | import edu.ucsb.cs156.frontiers.repositories.RosterStudentRepository; | |
12 | import edu.ucsb.cs156.frontiers.repositories.TeamMemberRepository; | |
13 | import edu.ucsb.cs156.frontiers.repositories.TeamRepository; | |
14 | import io.swagger.v3.oas.annotations.Operation; | |
15 | import io.swagger.v3.oas.annotations.Parameter; | |
16 | import io.swagger.v3.oas.annotations.tags.Tag; | |
17 | import java.io.BufferedInputStream; | |
18 | import java.io.IOException; | |
19 | import java.io.InputStream; | |
20 | import java.io.InputStreamReader; | |
21 | import java.util.*; | |
22 | import lombok.extern.slf4j.Slf4j; | |
23 | import org.springframework.beans.factory.annotation.Autowired; | |
24 | import org.springframework.http.HttpStatus; | |
25 | import org.springframework.http.ResponseEntity; | |
26 | import org.springframework.security.access.prepost.PreAuthorize; | |
27 | import org.springframework.transaction.annotation.Transactional; | |
28 | import org.springframework.web.bind.annotation.*; | |
29 | import org.springframework.web.multipart.MultipartFile; | |
30 | import org.springframework.web.server.ResponseStatusException; | |
31 | ||
32 | @Tag(name = "Teams") | |
33 | @RequestMapping("/api/teams") | |
34 | @RestController | |
35 | @Slf4j | |
36 | public class TeamsController extends ApiController { | |
37 | ||
38 | @Autowired private TeamRepository teamRepository; | |
39 | ||
40 | @Autowired private TeamMemberRepository teamMemberRepository; | |
41 | ||
42 | @Autowired private CourseRepository courseRepository; | |
43 | ||
44 | @Autowired private RosterStudentRepository rosterStudentRepository; | |
45 | ||
46 | public record TeamMemberResult( | |
47 | TeamMember teamMember, TeamMemberStatus status, String rejectedEmail) { | |
48 | public TeamMemberResult(TeamMember teamMember, TeamMemberStatus status) { | |
49 | this(teamMember, status, null); | |
50 | } | |
51 | ||
52 | public TeamMemberResult(String rejectedEmail) { | |
53 | this(null, TeamMemberStatus.MISSING, rejectedEmail); | |
54 | } | |
55 | } | |
56 | ||
57 | public record TeamCreationResponse( | |
58 | TeamSourceType typeMatched, Integer created, Integer existing, List<String> rejected) {} | |
59 | ||
60 | public record TeamMemberMapping( | |
61 | Long teamId, | |
62 | String teamName, | |
63 | Long rosterStudentId, | |
64 | String email, | |
65 | String firstName, | |
66 | String lastName, | |
67 | String githubLogin) { | |
68 | public static TeamMemberMapping from(TeamMember member) { | |
69 |
1
1. from : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController$TeamMemberMapping::from → KILLED |
return new TeamMemberMapping( |
70 | member.getTeam().getId(), | |
71 | member.getTeam().getName(), | |
72 | member.getRosterStudent().getId(), | |
73 | member.getRosterStudent().getEmail(), | |
74 | member.getRosterStudent().getFirstName(), | |
75 | member.getRosterStudent().getLastName(), | |
76 | member.getRosterStudent().getGithubLogin()); | |
77 | } | |
78 | } | |
79 | ||
80 | /** | |
81 | * This method creates a new Team. | |
82 | * | |
83 | * @param name the name of the team | |
84 | * @param courseId the ID of the course this team belongs to | |
85 | * @return the created team | |
86 | */ | |
87 | @Operation(summary = "Create a new team") | |
88 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
89 | @PostMapping("/post") | |
90 | public Team postTeam( | |
91 | @Parameter(name = "name") @RequestParam String name, | |
92 | @Parameter(name = "courseId") @RequestParam Long courseId) { | |
93 | ||
94 | Course course = | |
95 | courseRepository | |
96 | .findById(courseId) | |
97 |
1
1. lambda$postTeam$0 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$postTeam$0 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId)); |
98 | ||
99 | Team team = Team.builder().name(name).course(course).build(); | |
100 | ||
101 |
1
1. postTeam : negated conditional → KILLED |
if (teamRepository.findByCourseIdAndName(course.getId(), name).isPresent()) { |
102 | throw new ResponseStatusException( | |
103 | HttpStatus.CONFLICT, "Team with name %s already exists".formatted(name)); | |
104 | } else { | |
105 | team = teamRepository.save(team); | |
106 | } | |
107 | ||
108 |
1
1. postTeam : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::postTeam → KILLED |
return team; |
109 | } | |
110 | ||
111 | /** | |
112 | * Upload teams in CSV format (team, email) It is important to keep the code in this method | |
113 | * consistent with the code for adding a single roster student | |
114 | * | |
115 | * @param courseId course the teams are for | |
116 | * @param file csv file with roster student emails and team assignments | |
117 | * @return Count of students added to teams, already existing, and rejected students | |
118 | */ | |
119 | @Operation(summary = "Upload team assignments; CSV in format team,email") | |
120 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
121 | @PostMapping( | |
122 | value = "/upload/csv", | |
123 | consumes = {"multipart/form-data"}) | |
124 | public ResponseEntity<TeamCreationResponse> uploadTeamsCsv( | |
125 | @Parameter(name = "courseId") @RequestParam Long courseId, | |
126 | @Parameter(name = "file") @RequestParam("file") MultipartFile file) | |
127 | throws IOException, CsvException { | |
128 | ||
129 | Course course = | |
130 | courseRepository | |
131 | .findById(courseId) | |
132 |
1
1. lambda$uploadTeamsCsv$1 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$uploadTeamsCsv$1 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId.toString())); |
133 | ||
134 | int counts[] = {0, 0}; | |
135 | ||
136 | List<String> failed = new ArrayList<>(); | |
137 | ||
138 | try (InputStream inputStream = new BufferedInputStream(file.getInputStream()); | |
139 | InputStreamReader reader = new InputStreamReader(inputStream); | |
140 | CSVReader csvReader = new CSVReader(reader); ) { | |
141 | ||
142 | String[] headers = csvReader.readNext(); | |
143 | TeamSourceType sourceType = getRosterSourceType(headers); | |
144 | List<String[]> myEntries = csvReader.readAll(); | |
145 | for (String[] row : myEntries) { | |
146 | TeamMemberResult rowResult = fromCSVRow(row, sourceType, course); | |
147 |
1
1. uploadTeamsCsv : negated conditional → KILLED |
if (rowResult.status == TeamMemberStatus.MISSING) { |
148 | failed.add(rowResult.rejectedEmail); | |
149 | } else { | |
150 |
1
1. uploadTeamsCsv : Replaced integer addition with subtraction → KILLED |
counts[rowResult.status.ordinal()]++; |
151 | } | |
152 | } | |
153 | TeamCreationResponse response = | |
154 | new TeamCreationResponse( | |
155 | sourceType, | |
156 | counts[TeamMemberStatus.CREATED.ordinal()], | |
157 | counts[TeamMemberStatus.EXISTS.ordinal()], | |
158 | failed); | |
159 | ||
160 |
1
1. uploadTeamsCsv : negated conditional → KILLED |
if (!failed.isEmpty()) { |
161 | return ResponseEntity.status(HttpStatus.CONFLICT).body(response); | |
162 | } else { | |
163 | return ResponseEntity.ok(response); | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
168 | /** | |
169 | * This method returns a list of all teams for a course | |
170 | * | |
171 | * @return a list of all teams for a course | |
172 | */ | |
173 | @Operation(summary = "List all teams") | |
174 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
175 | @GetMapping("/all") | |
176 | public Iterable<Team> allTeams(@RequestParam Long courseId) { | |
177 | Iterable<Team> teams = teamRepository.findByCourseIdOrderByNameAsc(courseId); | |
178 |
1
1. allTeams : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/TeamsController::allTeams → KILLED |
return teams; |
179 | } | |
180 | ||
181 | /** | |
182 | * Retrieves a list of mappings between roster students and teams for a given course. Each mapping | |
183 | * represents a relationship between a team and its members. | |
184 | * | |
185 | * @param courseId the unique identifier of the course for which team mappings are retrieved | |
186 | * @return an iterable collection of {@code TeamMemberMapping} objects representing the mappings | |
187 | */ | |
188 | @Operation(summary = "List the mapping of Roster Students to Teams") | |
189 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
190 | @GetMapping("/mapping") | |
191 | public Iterable<TeamMemberMapping> teamMemberMapping(@RequestParam Long courseId) { | |
192 | List<Team> teams = | |
193 | courseRepository | |
194 | .findById(courseId) | |
195 |
1
1. lambda$teamMemberMapping$2 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$teamMemberMapping$2 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId)) |
196 | .getTeams(); | |
197 | List<TeamMemberMapping> mappings = new ArrayList<>(); | |
198 | for (Team team : teams) { | |
199 | for (TeamMember member : team.getTeamMembers()) { | |
200 | mappings.add(TeamMemberMapping.from(member)); | |
201 | } | |
202 | } | |
203 |
1
1. teamMemberMapping : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/TeamsController::teamMemberMapping → KILLED |
return mappings; |
204 | } | |
205 | ||
206 | /** | |
207 | * This method returns a single team by its id | |
208 | * | |
209 | * @param id the id of the team | |
210 | * @return the team | |
211 | */ | |
212 | @Operation(summary = "Get a single team") | |
213 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
214 | @GetMapping("") | |
215 | public Team getTeamById( | |
216 | @Parameter(name = "id") @RequestParam Long id, @RequestParam Long courseId) { | |
217 | Team team = | |
218 |
1
1. lambda$getTeamById$3 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$getTeamById$3 → KILLED |
teamRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(Team.class, id)); |
219 |
1
1. getTeamById : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::getTeamById → KILLED |
return team; |
220 | } | |
221 | ||
222 | /** | |
223 | * This method deletes a team by its id | |
224 | * | |
225 | * @param id the id of the team to delete | |
226 | * @return a message indicating the team was deleted | |
227 | */ | |
228 | @Operation(summary = "Delete a team") | |
229 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
230 | @DeleteMapping("") | |
231 | @Transactional | |
232 | public Object deleteTeam( | |
233 | @Parameter(name = "id") @RequestParam Long id, @RequestParam Long courseId) { | |
234 | Team team = | |
235 |
1
1. lambda$deleteTeam$4 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$deleteTeam$4 → KILLED |
teamRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(Team.class, id)); |
236 | ||
237 | // Handle team members that reference this team | |
238 |
1
1. deleteTeam : negated conditional → KILLED |
if (!team.getTeamMembers().isEmpty()) { |
239 | team.getTeamMembers() | |
240 |
1
1. deleteTeam : removed call to java/util/List::forEach → KILLED |
.forEach( |
241 | teamMember -> { | |
242 | // Remove from roster student's team members list | |
243 | teamMember.getRosterStudent().getTeamMembers().remove(teamMember); | |
244 |
1
1. lambda$deleteTeam$5 : removed call to edu/ucsb/cs156/frontiers/entities/TeamMember::setRosterStudent → KILLED |
teamMember.setRosterStudent(null); |
245 | }); | |
246 | } | |
247 | ||
248 | // Disconnect from course | |
249 | team.getCourse().getTeams().remove(team); | |
250 |
1
1. deleteTeam : removed call to edu/ucsb/cs156/frontiers/entities/Team::setCourse → KILLED |
team.setCourse(null); |
251 | ||
252 |
1
1. deleteTeam : removed call to edu/ucsb/cs156/frontiers/repositories/TeamRepository::delete → KILLED |
teamRepository.delete(team); |
253 |
1
1. deleteTeam : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::deleteTeam → KILLED |
return genericMessage("Team with id %s deleted".formatted(id)); |
254 | } | |
255 | ||
256 | /** | |
257 | * This method adds a roster student as a team member | |
258 | * | |
259 | * @param teamId the ID of the team | |
260 | * @param rosterStudentId the ID of the roster student to add | |
261 | * @return the created team member | |
262 | */ | |
263 | @Operation(summary = "Add a roster student to a team") | |
264 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
265 | @PostMapping("/addMember") | |
266 | public TeamMember addTeamMember( | |
267 | @Parameter(name = "teamId") @RequestParam Long teamId, | |
268 | @Parameter(name = "rosterStudentId") @RequestParam Long rosterStudentId, | |
269 | @Parameter(name = "courseId") @RequestParam Long courseId) { | |
270 | ||
271 | Team team = | |
272 | teamRepository | |
273 | .findById(teamId) | |
274 |
1
1. lambda$addTeamMember$6 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$addTeamMember$6 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Team.class, teamId)); |
275 | ||
276 | RosterStudent rosterStudent = | |
277 | rosterStudentRepository | |
278 | .findById(rosterStudentId) | |
279 |
1
1. lambda$addTeamMember$7 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$addTeamMember$7 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(RosterStudent.class, rosterStudentId)); |
280 | ||
281 |
1
1. addTeamMember : negated conditional → KILLED |
if (!team.getCourse().getId().equals(courseId)) { |
282 | throw new ResponseStatusException( | |
283 | HttpStatus.BAD_REQUEST, "Team is not from course %d".formatted(courseId)); | |
284 | } | |
285 |
1
1. addTeamMember : negated conditional → KILLED |
if (!rosterStudent.getCourse().getId().equals(courseId)) { |
286 | throw new ResponseStatusException( | |
287 | HttpStatus.BAD_REQUEST, "Roster student is not from course %d".formatted(courseId)); | |
288 | } | |
289 | ||
290 |
1
1. addTeamMember : negated conditional → KILLED |
if (teamMemberRepository.findByTeamAndRosterStudent(team, rosterStudent).isPresent()) { |
291 | throw new ResponseStatusException( | |
292 | HttpStatus.CONFLICT, | |
293 | "Team member already exists for team %s and roster student %s" | |
294 | .formatted(team.getName(), rosterStudent.getEmail())); | |
295 | } | |
296 | TeamMember teamMember = TeamMember.builder().team(team).rosterStudent(rosterStudent).build(); | |
297 | TeamMember savedTeamMember = teamMemberRepository.save(teamMember); | |
298 | ||
299 |
1
1. addTeamMember : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::addTeamMember → KILLED |
return savedTeamMember; |
300 | } | |
301 | ||
302 | /** | |
303 | * This method removes a team member | |
304 | * | |
305 | * @param teamMemberId the ID of the team member to remove | |
306 | * @return a message indicating the team member was removed | |
307 | */ | |
308 | @Operation(summary = "Remove a team member") | |
309 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
310 | @DeleteMapping("/removeMember") | |
311 | @Transactional | |
312 | public Object removeTeamMember( | |
313 | @Parameter(name = "teamMemberId") @RequestParam Long teamMemberId, | |
314 | @Parameter(name = "courseId") @RequestParam Long courseId) { | |
315 | TeamMember teamMember = | |
316 | teamMemberRepository | |
317 | .findById(teamMemberId) | |
318 |
1
1. lambda$removeTeamMember$8 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::lambda$removeTeamMember$8 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(TeamMember.class, teamMemberId)); |
319 | Team team = teamMember.getTeam(); | |
320 | RosterStudent rosterStudent = teamMember.getRosterStudent(); | |
321 | team.getTeamMembers().remove(teamMember); | |
322 | rosterStudent.getTeamMembers().remove(teamMember); | |
323 |
1
1. removeTeamMember : removed call to edu/ucsb/cs156/frontiers/repositories/TeamMemberRepository::delete → KILLED |
teamMemberRepository.delete(teamMember); |
324 | teamRepository.save(team); | |
325 | rosterStudentRepository.save(rosterStudent); | |
326 |
1
1. removeTeamMember : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::removeTeamMember → KILLED |
return genericMessage("Team member with id %s deleted".formatted(teamMemberId)); |
327 | } | |
328 | ||
329 | public enum TeamSourceType { | |
330 | SIMPLE, | |
331 | UNKNOWN | |
332 | } | |
333 | ||
334 | public enum TeamMemberStatus { | |
335 | CREATED, | |
336 | EXISTS, | |
337 | MISSING | |
338 | } | |
339 | ||
340 | public static final String SIMPLE_HEADERS = "team,email"; | |
341 | ||
342 | public TeamSourceType getRosterSourceType(String[] headers) { | |
343 | ||
344 | Map<TeamSourceType, String[]> sourceTypeToHeaders = new HashMap<>(); | |
345 | ||
346 | sourceTypeToHeaders.put(TeamSourceType.SIMPLE, SIMPLE_HEADERS.split(",")); | |
347 | ||
348 | for (Map.Entry<TeamSourceType, String[]> entry : sourceTypeToHeaders.entrySet()) { | |
349 | TeamSourceType type = entry.getKey(); | |
350 | String[] expectedHeaders = entry.getValue(); | |
351 |
2
1. getRosterSourceType : negated conditional → KILLED 2. getRosterSourceType : changed conditional boundary → KILLED |
if (headers.length >= expectedHeaders.length) { |
352 | boolean matches = true; | |
353 |
2
1. getRosterSourceType : changed conditional boundary → KILLED 2. getRosterSourceType : negated conditional → KILLED |
for (int i = 0; i < expectedHeaders.length; i++) { |
354 |
1
1. getRosterSourceType : negated conditional → KILLED |
if (!expectedHeaders[i].equalsIgnoreCase(headers[i])) { |
355 | matches = false; | |
356 | break; | |
357 | } | |
358 | } | |
359 |
1
1. getRosterSourceType : negated conditional → KILLED |
if (matches) { |
360 |
1
1. getRosterSourceType : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::getRosterSourceType → KILLED |
return type; |
361 | } | |
362 | } | |
363 | } | |
364 | // If no known type matches, throw | |
365 | throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown Roster Source Type"); | |
366 | } | |
367 | ||
368 | public TeamMemberResult fromCSVRow(String[] row, TeamSourceType sourceType, Course course) { | |
369 | // No if statements because this is the only possible value to enter here at the moment. Replace | |
370 | // with if when more | |
371 | // Formats are added. | |
372 |
1
1. fromCSVRow : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::fromCSVRow → KILLED |
return teamMemberFromSimpleCsv(row, course); |
373 | } | |
374 | ||
375 | public TeamMemberResult teamMemberFromSimpleCsv(String[] row, Course course) { | |
376 | Optional<RosterStudent> student = | |
377 | rosterStudentRepository.findByCourseIdAndEmail(course.getId(), row[1]); | |
378 | Optional<Team> team = teamRepository.findByCourseIdAndName(course.getId(), row[0]); | |
379 |
2
1. teamMemberFromSimpleCsv : negated conditional → KILLED 2. teamMemberFromSimpleCsv : negated conditional → KILLED |
if (student.isPresent() && team.isPresent()) { |
380 | Optional<TeamMember> teamMember = | |
381 | teamMemberRepository.findByTeamAndRosterStudent(team.get(), student.get()); | |
382 |
1
1. teamMemberFromSimpleCsv : negated conditional → KILLED |
if (teamMember.isPresent()) { |
383 |
1
1. teamMemberFromSimpleCsv : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::teamMemberFromSimpleCsv → KILLED |
return new TeamMemberResult(teamMember.get(), TeamMemberStatus.EXISTS); |
384 | } else { | |
385 | TeamMember teamMemberToSave = | |
386 | TeamMember.builder().team(team.get()).rosterStudent(student.get()).build(); | |
387 | TeamMember savedTeamMember = teamMemberRepository.save(teamMemberToSave); | |
388 |
1
1. teamMemberFromSimpleCsv : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::teamMemberFromSimpleCsv → KILLED |
return new TeamMemberResult(savedTeamMember, TeamMemberStatus.CREATED); |
389 | } | |
390 |
1
1. teamMemberFromSimpleCsv : negated conditional → KILLED |
} else if (student.isPresent()) { |
391 | Team teamToSave = Team.builder().name(row[0]).course(course).build(); | |
392 | teamRepository.save(teamToSave); | |
393 | TeamMember saveTeamMember = | |
394 | TeamMember.builder().team(teamToSave).rosterStudent(student.get()).build(); | |
395 | teamMemberRepository.save(saveTeamMember); | |
396 |
1
1. teamMemberFromSimpleCsv : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::teamMemberFromSimpleCsv → KILLED |
return new TeamMemberResult(saveTeamMember, TeamMemberStatus.CREATED); |
397 | } else { | |
398 |
1
1. teamMemberFromSimpleCsv : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/TeamsController::teamMemberFromSimpleCsv → KILLED |
return new TeamMemberResult(row[1]); |
399 | } | |
400 | } | |
401 | } | |
Mutations | ||
69 |
1.1 |
|
97 |
1.1 |
|
101 |
1.1 |
|
108 |
1.1 |
|
132 |
1.1 |
|
147 |
1.1 |
|
150 |
1.1 |
|
160 |
1.1 |
|
178 |
1.1 |
|
195 |
1.1 |
|
203 |
1.1 |
|
218 |
1.1 |
|
219 |
1.1 |
|
235 |
1.1 |
|
238 |
1.1 |
|
240 |
1.1 |
|
244 |
1.1 |
|
250 |
1.1 |
|
252 |
1.1 |
|
253 |
1.1 |
|
274 |
1.1 |
|
279 |
1.1 |
|
281 |
1.1 |
|
285 |
1.1 |
|
290 |
1.1 |
|
299 |
1.1 |
|
318 |
1.1 |
|
323 |
1.1 |
|
326 |
1.1 |
|
351 |
1.1 2.2 |
|
353 |
1.1 2.2 |
|
354 |
1.1 |
|
359 |
1.1 |
|
360 |
1.1 |
|
372 |
1.1 |
|
379 |
1.1 2.2 |
|
382 |
1.1 |
|
383 |
1.1 |
|
388 |
1.1 |
|
390 |
1.1 |
|
396 |
1.1 |
|
398 |
1.1 |